From 28c95dfba845ab5f7a6c48ad1ee8d33c28cddaee Mon Sep 17 00:00:00 2001 From: Andreas Wegscheider Date: Fri, 19 Sep 2025 11:13:42 +0200 Subject: [PATCH 1/4] add scanner - add rules - add highlighter - add metrics - start major refactor --- .gitignore | 5 +- Cargo.lock | 1702 ++++++++++++++++++++++++++++++ Cargo.toml | 10 +- README.md | 112 +- bin/scanner.rs | 69 ++ src/main.rs => bin/server.rs | 160 +-- examples/check_rules.rs | 77 +- examples/example1.rs | 30 +- examples/print_visitor.rs | 37 +- src/debug.rs | 20 +- src/document.rs | 282 +---- src/highlighter.rs | 115 ++ src/lexer/cursor.rs | 34 +- src/lexer/mod.rs | 28 +- src/lexer/token.rs | 2 - src/lib.rs | 2 + src/measures/code_blocks.rs | 60 ++ src/measures/loc.rs | 86 ++ src/measures/mod.rs | 2 + src/parser/assignment.rs | 42 +- src/parser/expression.rs | 200 +--- src/parser/label.rs | 29 +- src/parser/madcall.rs | 112 ++ src/parser/madenvironment.rs | 91 +- src/parser/madexec.rs | 16 +- src/parser/madgeneric.rs | 502 ++++++--- src/parser/madif.rs | 22 +- src/parser/madmacro.rs | 202 +--- src/parser/mod.rs | 157 +-- src/parser/problem.rs | 67 +- src/rules/collect_labels.rs | 60 ++ src/rules/macro_args.rs | 102 ++ src/rules/macro_variables.rs | 100 ++ src/rules/mod.rs | 3 + src/rules/undefined_exec_call.rs | 66 +- src/visitor.rs | 164 ++- tests/input1.madx | 6 + tests/macro_args.madx | 14 + tests/with_errors.madx | 7 +- 39 files changed, 3379 insertions(+), 1416 deletions(-) create mode 100644 Cargo.lock create mode 100644 bin/scanner.rs rename src/main.rs => bin/server.rs (67%) create mode 100644 src/highlighter.rs create mode 100644 src/measures/code_blocks.rs create mode 100644 src/measures/loc.rs create mode 100644 src/measures/mod.rs create mode 100644 src/parser/madcall.rs create mode 100644 src/rules/collect_labels.rs create mode 100644 src/rules/macro_args.rs create mode 100644 src/rules/macro_variables.rs create mode 100644 tests/input1.madx create mode 100644 tests/macro_args.madx diff --git a/.gitignore b/.gitignore index 96ef6c0..8c5acea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /target -Cargo.lock +scan.sh + +.vscode +.scannerwork diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fb010d1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1702 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "atomic_refcell" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "destructure_traitobject" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +dependencies = [ + "equivalent", + "hashbrown 0.15.5", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +dependencies = [ + "serde", +] + +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0816135ae15bd0391cf284eab37e6e3ee0a6ee63d2ceeb659862bd8d0a984ca6" +dependencies = [ + "anyhow", + "arc-swap", + "chrono", + "derivative", + "fnv", + "humantime", + "libc", + "log", + "log-mdc", + "once_cell", + "parking_lot", + "rand", + "serde", + "serde-value", + "serde_json", + "serde_yaml", + "thiserror", + "thread-id", + "typemap-ors", + "winapi", +] + +[[package]] +name = "lsp-types" +version = "0.94.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "serde_repr", + "url", +] + +[[package]] +name = "madxls" +version = "0.1.0" +dependencies = [ + "anyhow", + "atomic_refcell", + "clap", + "dashmap 6.1.0", + "log", + "log4rs", + "once_cell", + "tokio", + "tower-lsp", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.4", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thread-id" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-lsp" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508" +dependencies = [ + "async-trait", + "auto_impl", + "bytes", + "dashmap 5.5.3", + "futures", + "httparse", + "lsp-types", + "memchr", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tower", + "tower-lsp-macros", + "tracing", +] + +[[package]] +name = "tower-lsp-macros" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typemap-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" +dependencies = [ + "unsafe-any-ors", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unsafe-any-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" +dependencies = [ + "destructure_traitobject", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] diff --git a/Cargo.toml b/Cargo.toml index 10a0b45..3819fbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,18 @@ edition = "2021" [lib] +[[bin]] +name = "madx_scanner" +path = "bin/scanner.rs" + +[[bin]] +name = "madxls" +path = "bin/server.rs" + [dependencies] atomic_refcell = "*" tokio = { version = "1.28.2", features = ["io-util", "io-std", "macros", "rt-multi-thread"] } -tower-lsp = "0.19.0" +tower-lsp = "0.20.0" dashmap = "*" log = "*" log4rs = "*" diff --git a/README.md b/README.md index 8b6d941..55ceaf0 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,70 @@ # MADX-LS -An LSP implementation for the [MADX](http://mad.web.cern.ch/mad/) scripting language. +A comprehensive language toolchain for the [MADX](http://mad.web.cern.ch/mad/) scripting language, providing both IDE integration and static analysis capabilities. -## Features +## Overview -- [x] Semantic highlighting (in progress, most commands are done) -- [ ] Hover - - [x] defined macros - - [ ] built-in commands - - [ ] variables in scope -- [ ] Errors - - [ ] syntax errors - - [x] command usage -- [ ] Hints -- [ ] Jump to definition +MADX-LS is a Rust-based toolkit designed to enhance the development experience for MADX (Methodical Accelerator Design) scripts. The project consists of two powerful binaries that serve different aspects of the development workflow: + +### 🚀 **Scanner** - Static Analysis & Code Quality +A command-line tool that performs comprehensive analysis of MADX code, delivering: +- **Syntax Error Detection**: Identify and report syntax issues in your MADX scripts +- **Code Quality Analysis**: Detect bad practices and potential improvements +- **Code Metrics**: Generate detailed metrics about your codebase +- **SonarQube Integration**: Seamlessly integrates with SonarQube servers for continuous code quality monitoring + +### 🔧 **Server** - Language Server Protocol (LSP) +An LSP implementation that brings intelligent code assistance to your favorite IDE. Currently under heavy refactoring to improve stability and feature completeness. + +> **⚠️ Note**: The LSP server is currently undergoing significant refactoring and may not be fully functional. We recommend using the scanner for immediate code analysis needs. + +## Supported Editors + +The LSP server is designed to work with any editor that supports the Language Server Protocol, including: + +- **Visual Studio Code** - Full IDE experience with syntax highlighting, error detection, and IntelliSense +- **Vim/Neovim** - Lightweight integration for terminal-based development +- **Emacs** - Rich editing experience with LSP support +- **And many more** - Any editor with LSP client support + +## Installation + +### Prerequisites +- [Rust toolchain](https://www.rust-lang.org/learn/get-started) (latest stable version) + +### Install from Source + +- Scanner +```bash +cargo install --git https://github.com/awegsche/madxls.git --bin madx_scanner +``` +- LSP +```bash +cargo install --git https://github.com/awegsche/madxls.git --bin madxls +``` + +> **Tip**: After installation, ensure the Cargo binary directory is in your PATH. You can find it with `cargo --list` and add it to your shell profile. ## Usage -- Install the [rust toolchain ](https://www.rust-lang.org/learn/get-started) -- Install with cargo - ``` sh - cargo install --git https://github.com/awegsche/madxls.git - ``` -- Maybe you then have to add the cargo bin dir to PATH (todo: some hints on how to do that). -- Use it with your code editor. If your code editor supports the LSP, this is just a matter of -adding a corresponding entry in your configuration file. Some examples are listed below: +### Scanner (Recommended) +The scanner is the most stable and feature-complete component: -## Code Editor Specific Usage +```bash +# Analyze a single file +madx_scanner analyze path/to/your/script.madx -### neovim +# Analyze a directory +madx_scanner analyze path/to/project/ -Add the following to your configuration (todo: simplify this): +# Generate detailed metrics +madx_scanner metrics path/to/your/script.madx +``` +### LSP Server (Under Development) +Once the refactoring is complete, the LSP server will provide real-time assistance in your editor. + +#### Neovim Configuration ```lua function StartMadx() vim.lsp.start({ @@ -44,13 +77,36 @@ vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { pattern = {"*.madx"}, callback = StartMadx, }) - ``` -### vscode +#### VS Code +A dedicated VS Code extension is planned for seamless integration. + +#### Emacs +Configure using your preferred LSP client (e.g., `lsp-mode` or `eglot`). + +## SonarQube Integration + +The scanner includes a SonarQube plugin that enables continuous code quality monitoring: + +- **On-Premise SonarQube**: Full support for self-hosted SonarQube instances +- **Cloud Support**: Currently limited due to plugin deployment constraints (coming soon) + +## Contributing -Wait for a madxls plugin +We welcome contributions! The project is actively developed, and we're particularly interested in: +- LSP server stability improvements +- Additional code quality rules +- Enhanced editor integrations +- SonarQube cloud deployment solutions -### emacs +## Roadmap -cf. emacs LSP configuration +- [ ] Complete LSP server refactoring +- [ ] Enhanced semantic highlighting + - [x] Sonarqube + - [ ] LSP +- [ ] Hover documentation for built-in commands +- [ ] Jump-to-definition functionality +- [ ] SonarQube cloud plugin deployment +- [ ] VS Code extension development \ No newline at end of file diff --git a/bin/scanner.rs b/bin/scanner.rs new file mode 100644 index 0000000..38d7438 --- /dev/null +++ b/bin/scanner.rs @@ -0,0 +1,69 @@ +use clap::Parser; +use madxls::highlighter::Highlighter; +use madxls::lexer::print_range; +use madxls::measures::code_blocks::CodeBlocks; +use madxls::measures::loc::Loc; +use madxls::parser; +use madxls::rules::{ + collect_labels::CollectLabels, macro_args::MacroArgs, macro_variables::MacroVars, + undefined_exec_call::UndefinedExecCall, +}; + +#[derive(Parser, Debug)] +#[command(version, about)] +struct Args { + #[arg(long)] + pub input_file: Option, + + #[arg(long, default_value = "false")] + pub highlight: bool, +} + +fn main() { + let args = Args::parse(); + + if let Some(file) = args.input_file { + let parser = parser::Parser::from_path(file).unwrap(); + + for mf in parser.get_missing_files().iter() { + println!("MissingFile | {}", print_range(mf)); + } + + let collect_labels = CollectLabels::new(&parser); + let missing_callee = UndefinedExecCall::new(&parser, collect_labels.get_labels()); + let macro_args = MacroArgs::new(&parser); + let macro_vars = MacroVars::new(&parser); + + for p in missing_callee.get_problems().iter() { + let range = parser.lexer.cursor_range_to_text_range(p); + + println!( + "MissingCallee | ({}, {}) -- ({}, {})", + range.start.line, range.start.character, range.end.line, range.end.character + ); + } + for ma in macro_args.get_problems().iter() { + println!("{}", ma); + } + for mv in macro_vars.get_problems().iter() { + println!("{}", mv); + } + + if args.highlight { + let highlighter = Highlighter::new(&parser); + for h in highlighter.highlights.iter() { + println!("{}", h); + } + } + + let loc = Loc::new(&parser); + + println!("loc: {}", loc.lines.len()); + println!("commentloc: {}", loc.commentlines.len()); + + let code_blocks = CodeBlocks::new(&parser); + println!("macros: {}", code_blocks.macros); + println!("statements: {}", code_blocks.statements); + println!("elements: {}", code_blocks.elements); + } +} diff --git a/src/main.rs b/bin/server.rs similarity index 67% rename from src/main.rs rename to bin/server.rs index c381cd1..effc6de 100644 --- a/src/main.rs +++ b/bin/server.rs @@ -8,22 +8,13 @@ use log4rs::config::Appender; use log4rs::config::Root; use log4rs::encode::pattern::PatternEncoder; use log4rs::Config; -use parser::MaybeProblem; use parser::Problem; use parser::LEGEND_TYPE; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::*; use tower_lsp::{Client, LanguageServer, LspService, Server}; -pub mod document; -pub mod error; -pub mod lexer; -pub mod parser; -pub mod rules; -pub mod semantic_tokens; -pub mod visitor; - -pub mod debug; +use madxls::*; #[derive(Parser, Debug)] #[command(version, about)] @@ -123,7 +114,7 @@ impl LanguageServer for Backend { }) } - async fn initialized(&self, p: InitializedParams) { + async fn initialized(&self, _: InitializedParams) { log::info!("initialized"); self.client .log_message(MessageType::INFO, "server initialized!") @@ -153,11 +144,11 @@ impl LanguageServer for Backend { .documents .get(¶ms.text_document_position_params.text_document.uri) { - let hi = doc.get_document_highlights(¶ms.text_document_position_params.position); - if let Ok(Some(h)) = &hi { - log::debug!("get some highlights: {}", h.len()); - } - return hi; + //let hi = doc.get_document_highlights(¶ms.text_document_position_params.position); + //if let Ok(Some(h)) = &hi { + // log::debug!("get some highlights: {}", h.len()); + //} + //return hi; } Ok(None) } @@ -177,6 +168,7 @@ impl LanguageServer for Backend { document::Document::new(Some(uri.clone()), params.text_document.text.as_bytes()); // check the includes + /* let includes = document.parser.includes.clone(); let docs = self.documents.clone(); tokio::spawn(async move { @@ -184,6 +176,7 @@ impl LanguageServer for Backend { reload_includes(incl, &docs); } }); + */ self.documents.insert(uri.clone(), document); } @@ -195,6 +188,7 @@ impl LanguageServer for Backend { document.reload(params.content_changes[0].text.as_bytes()); //self.client.publish_diagnostics(params.text_document.uri.clone(), document.get_diagnostics(), None).await; // check the includes + /* let includes = document.parser.includes.clone(); let docs = self.documents.clone(); tokio::spawn(async move { @@ -202,6 +196,7 @@ impl LanguageServer for Backend { reload_includes(incl, &docs); } }); + */ } self.resubmit_diagnostics(¶ms.text_document.uri).await; } @@ -212,29 +207,19 @@ impl LanguageServer for Backend { .documents .get(¶ms.text_document_position_params.text_document.uri) { - self.resubmit_diagnostics(¶ms.text_document_position_params.text_document.uri) - .await; - let labels = doc.get_labels_under_cursor(params.text_document_position_params.position); - log::debug!("check hover for: {:?}", labels); - let mut items = Vec::new(); - doc.get_hover(&labels, &mut items, None); - - log::debug!("includes in file: {}", doc.parser.includes.len()); - log::debug!("docs loaded: {}", self.documents.len()); - - for (uri, incl) in doc - .parser - .includes - .iter() - .filter_map(|uri| Some((uri, self.documents.get(uri)?))) - { - log::debug!("checking in {}", uri.path()); - incl.get_hover(&labels, &mut items, Some(uri)); - } - return Ok(Some(Hover { - contents: HoverContents::Array(items), - range: None, - })); + //self.resubmit_diagnostics(¶ms.text_document_position_params.text_document.uri) + // .await; + //let labels = doc.get_labels_under_cursor(params.text_document_position_params.position); + //log::debug!("check hover for: {:?}", labels); + //let mut items = Vec::new(); + //doc.get_hover(&labels, &mut items, None); + + //log::debug!("docs loaded: {}", self.documents.len()); + + //return Ok(Some(Hover { + // contents: HoverContents::Array(items), + // range: None, + //})); } Ok(None) } @@ -245,7 +230,7 @@ impl LanguageServer for Backend { ) -> Result> { log::info!("semantic tokens full"); if let Some(document) = self.documents.get(¶ms.text_document.uri) { - return document.get_semantic_tokens(); + //return document.get_semantic_tokens(); } Ok(None) } @@ -258,39 +243,15 @@ impl LanguageServer for Backend { fn recheck_problems( uri: &Url, documents: &Arc>, - problems: &mut Vec, + problems: &mut Vec, ) { if let Some(doc) = documents.get(uri) { - log::debug!("rechecking {}", uri.path()); - for incl in doc.parser.includes.iter() { - recheck_problems(incl, documents, problems); - } - - log::debug!("problems:"); - for p in problems.iter_mut() { - match p.problem.as_ref() { - Some(Problem::MissingCallee(c, _)) => { - // look for callee in labels - log::debug!("check problem {}", String::from_utf8(c.clone()).unwrap()); - for (label, _) in doc.parser.labels.iter() { - if label == c { - log::debug!("-> match"); - p.problem = None; - break; - } - } - } - _ => {} - } - } - log::debug!( - "not-None: {}", - problems.iter().filter(|p| p.problem.is_some()).count() - ); + // do something } } fn reload_includes(uri: Url, documents: &Arc>) { + /* log::debug!("reloading includes for {}", uri.path()); if !documents.contains_key(&uri) { if let Ok(doc) = document::Document::open(uri.path()) { @@ -302,6 +263,7 @@ fn reload_includes(uri: Url, documents: &Arc>) documents.insert(uri.clone(), doc); } } + */ } impl Backend { @@ -309,22 +271,15 @@ impl Backend { log::debug!("try resubmit"); if let Some(doc) = self.documents.get(uri) { - let mut problems = doc.get_diagnostics(); - - for p in problems.iter_mut() { - match p.problem.as_mut() { - Some(Problem::MissingCallee(s, r)) => { - *s = doc.parser.get_element_bytes(r).to_vec() - } - _ => {} - } - } - recheck_problems(uri, &self.documents, &mut problems); - - log::debug!("publishing"); - self.client - .publish_diagnostics(uri.clone(), diagnostics_from_problems(&problems), None) - .await; + ////let problems = doc.get_diagnostics(); + + //self.client + // .publish_diagnostics( + // uri.clone(), + // diagnostics_from_problems(&problems, &doc.parser), + // None, + // ) + // .await; } } } @@ -335,41 +290,14 @@ fn get_completions( url: &Url, documents: &Arc>, ) { - if let Some(doc) = documents.get(url) { - items.extend(doc.get_completion(pos).into_iter()); - - for incl in doc.parser.includes.iter() { - get_completions(items, None, &incl, documents); - } - } + if let Some(doc) = documents.get(url) {} } -fn diagnostics_from_problems(problems: &[MaybeProblem]) -> Vec { - problems - .iter() - .filter_map(|p| { - let Some(problem) = p.problem.as_ref() else { - return None; - }; - - let severity = match problem { - Problem::MissingCallee(_, _) => DiagnosticSeverity::ERROR, - Problem::InvalidParam(_) => DiagnosticSeverity::ERROR, - Problem::Error(_, _, _) => DiagnosticSeverity::ERROR, - Problem::Warning(_, _, _) => DiagnosticSeverity::WARNING, - Problem::Hint(_, _, _) => DiagnosticSeverity::HINT, - }; - Some(Diagnostic::new( - p.range, - Some(severity), - None, - None, - format!("{}", problem), - None, - None, - )) - }) - .collect() +fn diagnostics_from_problems( + problems: &[Problem], + parser: &crate::parser::Parser, +) -> Vec { + problems.iter().map(|p| p.to_diagnostic(parser)).collect() } async fn run_server() { diff --git a/examples/check_rules.rs b/examples/check_rules.rs index 7cc7897..812a7b4 100644 --- a/examples/check_rules.rs +++ b/examples/check_rules.rs @@ -1,8 +1,6 @@ use clap::Parser; -use madxls::{ - parser::{self, Problem}, - visitor::Visitor, -}; +use madxls::parser::{self}; +use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity}; #[derive(Parser, Debug)] #[command(version, about)] @@ -24,57 +22,42 @@ fn main() { } println!("----------------------------------------\n"); - println!("{} Labels", parser.labels.len()); - println!("- - - - - - - - - - "); - - for l in parser.labels.iter() { - println!("{:?}", l); - } - println!("----------------------------------------\n"); - - println!("{} Problems", parser.problems.len()); - println!("- - - - - - - - - - "); - - for p in parser.problems.iter() { - match p { - Problem::MissingCallee(c, range) => { - match parser - .labels - .iter() - .find(|(l, _)| parser.get_element_bytes(range) == **l) - { - None => println!("{:?}, {}", p, parser.get_element_str(range)), - Some(_) => {} - } - } - _ => {} - }; - } - - let mut visitor = madxls::visitor::PrintVisitor::new(&parser); - - for e in parser.get_elements() { - e.accept(&mut visitor); - } - println!("Visitor Output:\n{}", visitor.buffer); - println!("And now the rules"); println!("-----------------"); - let mut missing_callee = - madxls::rules::undefined_exec_call::UndefinedExecCall::new(&parser); + let collect_labels = madxls::rules::collect_labels::CollectLabels::new(&parser); - for e in parser.get_elements() { - println!("Visiting: {}", parser.get_element_str(e)); - e.accept(&mut missing_callee); + println!("Collected {} labels", collect_labels.get_labels().len()); + for l in collect_labels.get_labels().iter() { + println!("{}", String::from_utf8_lossy(l)); } + let missing_callee = madxls::rules::undefined_exec_call::UndefinedExecCall::new( + &parser, + collect_labels.get_labels(), + ); + println!("Found {} problems", missing_callee.get_problems().len()); for p in missing_callee.get_problems().iter() { - println!( - "Undefined exec call: {}", - parser.get_element_str(&(p.0, p.1)) - ); + let range = parser.lexer.cursor_range_to_text_range(p); + let severity = Some(DiagnosticSeverity::ERROR); + let code = None; + //let source = "madx"; + let message = format!("Undefined exec call: {}", parser.get_element_str(&p)); + + let diagnostic = Diagnostic { + range, + severity, + code, + code_description: None, + message, + source: None, + related_information: None, + tags: None, + data: None, + }; + + println!("{:?}", diagnostic); } } } diff --git a/examples/example1.rs b/examples/example1.rs index 6328a92..e340dc5 100644 --- a/examples/example1.rs +++ b/examples/example1.rs @@ -1,5 +1,5 @@ use clap::Parser; -use madxls::parser::{self, Problem}; +use madxls::parser::{self}; #[derive(Parser, Debug)] #[command(version, about)] @@ -20,33 +20,5 @@ fn main() { println!("{:?}", e); } println!("----------------------------------------\n"); - - println!("{} Labels", parser.labels.len()); - println!("- - - - - - - - - - "); - - for l in parser.labels.iter() { - println!("{:?}", l); - } - println!("----------------------------------------\n"); - - println!("{} Problems", parser.problems.len()); - println!("- - - - - - - - - - "); - - for p in parser.problems.iter() { - match p { - Problem::MissingCallee(c, range) => { - match parser - .labels - .iter() - .find(|(l, _)| parser.get_element_bytes(range) == **l) - { - None => println!("{:?}, {}", p, parser.get_element_str(range)), - Some(_) => {} - } - } - _ => {} - }; - } - return; } } diff --git a/examples/print_visitor.rs b/examples/print_visitor.rs index d38dfba..f45c34f 100644 --- a/examples/print_visitor.rs +++ b/examples/print_visitor.rs @@ -1,8 +1,5 @@ use clap::Parser; -use madxls::{ - parser::{self, Problem}, - visitor::Visitor, -}; +use madxls::parser::{self}; #[derive(Parser, Debug)] #[command(version, about)] @@ -24,38 +21,8 @@ fn main() { } println!("----------------------------------------\n"); - println!("{} Labels", parser.labels.len()); - println!("- - - - - - - - - - "); - - for l in parser.labels.iter() { - println!("{:?}", l); - } - println!("----------------------------------------\n"); - - println!("{} Problems", parser.problems.len()); - println!("- - - - - - - - - - "); + let visitor = madxls::visitor::PrintVisitor::new(&parser); - for p in parser.problems.iter() { - match p { - Problem::MissingCallee(c, range) => { - match parser - .labels - .iter() - .find(|(l, _)| parser.get_element_bytes(range) == **l) - { - None => println!("{:?}, {}", p, parser.get_element_str(range)), - Some(_) => {} - } - } - _ => {} - }; - } - - let mut visitor = madxls::visitor::PrintVisitor::new(&parser); - - for e in parser.get_elements() { - visitor.visit(e); - } println!("Visitor Output:\n{}", visitor.buffer); } } diff --git a/src/debug.rs b/src/debug.rs index 35ec92e..6df1b67 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -3,29 +3,11 @@ use std::time::Instant; use crate::parser::Parser; pub fn print_ast(file: String) { - let start = Instant::now(); println!("opening parser"); let parser = Parser::from_path(&file).unwrap(); - let opening_time= Instant::now() - start; + let opening_time = Instant::now() - start; println!("took {}ms", opening_time.as_millis()); - - let mut semantic_tokens = Vec::new(); - let mut pre_line = 0; - let mut pre_start = 0; - - println!("provide semantic tokens"); - - for e in parser.get_elements() { - e.to_semantic_token(&mut semantic_tokens, &mut pre_line, &mut pre_start, &parser); - } - - println!("done"); - - println!("{:#?}", parser.lexer.get_tokens()); - - println!("{:#?}", semantic_tokens); - } diff --git a/src/document.rs b/src/document.rs index 8ce0f8b..b4a549c 100644 --- a/src/document.rs +++ b/src/document.rs @@ -7,8 +7,7 @@ use tower_lsp::lsp_types::{ }; use crate::error::UTF8_PARSER_MSG; -use crate::lexer::HasRange; -use crate::parser::{Expression, MaybeProblem, Parser, Problem, GENERIC_BUILTINS}; +use crate::parser::{Expression, Parser, Problem, GENERIC_BUILTINS}; #[derive(Debug)] pub struct Document { @@ -33,197 +32,6 @@ impl Document { self.parser = Parser::from_bytes(text.to_vec(), uri); //self.parser.scan_includes(); } - - pub fn get_diagnostics(&self) -> Vec { - log::debug!("parser.problems:"); - for p in self.parser.problems.iter() { - match p { - Problem::MissingCallee(_, range) => { - log::debug!("MissingCalle: {}", self.parser.get_element_str(range)) - } - Problem::InvalidParam(range) => { - log::debug!("InvalidParam: {}", self.parser.get_element_str(range)) - } - Problem::Error(_, _, _) => {} - Problem::Warning(_, _, _) => {} - Problem::Hint(_, _, _) => {} - }; - } - - self.parser - .problems - .iter() - .map(|p| { - let range = match p { - crate::parser::Problem::MissingCallee(_, range) => range, - crate::parser::Problem::InvalidParam(range) => range, - crate::parser::Problem::Error(_, _, _) => todo!(), - crate::parser::Problem::Warning(_, _, _) => todo!(), - crate::parser::Problem::Hint(_, _, _) => todo!(), - }; - MaybeProblem { - problem: Some(p.clone()), - range: Range::new( - self.parser.lexer.cursor_pos_to_text_pos(range.0), - self.parser.lexer.cursor_pos_to_text_pos(range.1), - ), - } - }) - .collect() - } - - pub fn get_document_highlights( - &self, - pos: &Position, - ) -> Result>> { - let position = self.parser.lexer.cursor_pos_from_text_pos(*pos); - if let Some(exp) = self.parser.get_expression_at(position) { - let highlights = exp.get_highlights(&position, &self.parser); - - return Ok(Some( - highlights - .iter() - .map(|range| DocumentHighlight { - range: Range::new( - self.parser.lexer.cursor_pos_to_text_pos(range.0), - self.parser.lexer.cursor_pos_to_text_pos(range.1), - ), - kind: None, - }) - .collect(), - )); - } - Ok(None) - } - - pub fn get_semantic_tokens(&self) -> Result> { - let mut pre_line = 0; - let mut pre_start = 0; - //log::info!("{:#?}", parser.get_elements()); - log::info!("parser elements: {}", self.parser.get_elements().len()); - let mut data = Vec::new(); - for e in self.parser.get_elements() { - e.to_semantic_token(&mut data, &mut pre_line, &mut pre_start, &self.parser); - if let Expression::Exit(_) = e { - break; - } - } - //log::info!("data: {:#?}", data); - - let tokens = Some(SemanticTokensResult::Tokens(SemanticTokens { - data, - ..Default::default() - })); - Ok(tokens) - } - - pub fn get_labels_under_cursor(&self, position: Position) -> Vec<&[u8]> { - let pos = self.parser.lexer.cursor_pos_from_text_pos(position); - - self.parser - .get_elements() - .iter() - .filter_map(|e| e.get_label(&pos, &self.parser)) - .collect() - } - - /// gets the hover information for the given set of labels - pub fn get_hover( - &self, - labels: &Vec<&[u8]>, - items: &mut Vec, - infile: Option<&Url>, - ) { - for label in labels.iter() { - // first, look in named labels - if let Some(index) = self.parser.labels.get(*label) { - let comment = if *index > 0 { - sanitize_string_for_md( - self.parser - .get_element_str(&self.parser.get_elements()[*index - 1]) - .to_string(), - ) - } else { - String::new() - }; - - let element = &self.parser.get_elements()[*index]; - - let line = element.get_range().0.line(); - let location = match infile { - Some(uri) => { - format!("\"{}\", ", uri.path()) - } - None => String::new(), - }; - /* - let comment = match self.parser.get_elements()[*index - 1] { - Expression::Comment(range) => String::from_utf8(self.parser.get_element_bytes(&range).to_vec()).unwrap(), - _ => String::new() - }; - */ - - let signature = match element { - Expression::Label(l) => { - format!("`{}` : **LABEL**", self.parser.get_element_str(l)) - } - Expression::Macro(m) => format!( - "`{}{}` : **MACRO**", - self.parser.get_element_str(&m.name), - self.parser - .get_element_str(&(m.parenopen, m.parenclose + 1)), - ), - Expression::Assignment(a) => format!("`{}`", self.parser.get_element_str(a)), - Expression::String(_) => todo!(), - Expression::Comment(_) => todo!(), - Expression::Symbol(s) => s.clone(), - Expression::MadGeneric(_) => todo!(), - Expression::MadEnvironment(_) => todo!(), - Expression::Exit(_) => todo!(), - Expression::Operator(_) => todo!(), - Expression::TokenExp(_) => todo!(), - Expression::Exec(_) => todo!(), - Expression::If(_) => todo!(), - Expression::Noop(_) => todo!(), - }; - - items.push(MarkedString::String(format!( - "{}\n---\n{}\n---\ndefined in {}line {}\n\n", - signature, comment, location, line - ))); - } - } - } - - pub fn get_completion(&self, position: Option) -> Vec { - let mut items = Vec::new(); - for label in self.parser.labels.keys() { - items.push(CompletionItem { - label: String::from_utf8(label.clone()) - .unwrap_or_else(|_| UTF8_PARSER_MSG.to_string()), - kind: Some(CompletionItemKind::VARIABLE), - ..Default::default() - }); - } - - if let Some(position) = position { - let pos = self.parser.lexer.cursor_pos_from_text_pos(position); - log::debug!("completion triggered at {:#?}", pos); - for name in GENERIC_BUILTINS.keys() { - items.push(CompletionItem { - label: String::from_utf8(name.to_vec()) - .unwrap_or_else(|_| UTF8_PARSER_MSG.to_string()), - kind: Some(CompletionItemKind::FUNCTION), - ..Default::default() - }); - } - for e in self.parser.get_elements() { - e.get_completion(&pos, &mut items); - } - } - - items - } } pub fn sanitize_string_for_md(s: String) -> String { @@ -235,56 +43,6 @@ mod tests { use super::*; - #[test] - fn test_simple() { - let doc = Document::new( - None, - b"option, echo;\ntwiss, sequence=lhcb1, file=\"twiss.dat\";", - ); - - let _ = doc.get_semantic_tokens(); - let _ = doc.get_completion(Some(Position { - line: 0, - character: 0, - })); - } - - #[test] - fn test_incomplete_env() { - let doc = Document::new( - None, - b"option, echo;\nseqedit; flatten;\ntwiss, sequence = lhcb1;", - ); - - let _ = doc.get_semantic_tokens(); - let _ = doc.get_completion(Some(Position { - line: 1, - character: 10, - })); - } - - #[test] - fn test_incomplete_generic() { - let doc = Document::new(None, b"option, echo;\ncall, fi"); - - let st = doc.get_semantic_tokens(); - let completion = doc.get_completion(Some(Position { - line: 1, - character: 21, - })); - - for i in completion.iter() { - if i.label == "file" { - return; - } - } - - assert!(false, "didn't provide 'file' as possible completion\ncompletions are:\n{:?}\nsemantic tokens:\n{:?}", - completion.iter().filter(|c| c.kind.unwrap() == CompletionItemKind::FIELD).collect::>(), - st - ); - } - #[test] fn test_macros() { let elements = vec![ @@ -309,42 +67,4 @@ mod tests { ); } } - - #[test] - fn test_file_lhc_macros() { - let _doc = Document::new(None, include_bytes!("../tests/macros/lhc.macros.run3.madx")); - } - - #[test] - fn get_hover() { - let doc = Document::new( - None, - b" -do_twiss(a,b): macro = { twiss, sequence=lhcb1;}; -option, echo; - -exec, do_twiss(0, 0); - ", - ); - - let labels = doc.get_labels_under_cursor(Position::new(4, 9)); - - assert_eq!(labels, [b"do_twiss"]); - let mut items = vec![]; - let uri = Url::from_file_path("/home").unwrap(); - doc.get_hover(&labels, &mut items, Some(&uri)); - - for item in items.iter() { - if let MarkedString::String(s) = item { - if s == "`do_twiss(a,b)` : **MACRO**\n---\n\n---\ndefined in \"/home\", line 1" { - return; - } - } - } - assert!( - false, - "expected macro do_twiss in hover, items: {:?}", - items - ); - } } diff --git a/src/highlighter.rs b/src/highlighter.rs new file mode 100644 index 0000000..d3b8ed6 --- /dev/null +++ b/src/highlighter.rs @@ -0,0 +1,115 @@ +use std::fmt::Display; + +use tower_lsp::lsp_types::Range; + +use crate::{ + lexer::{print_range, CursorPosition}, + parser::Parser, + visitor::Visitor, +}; + +pub enum Highlight { + Keyword(Range), + Type(Range), + Class(Range), + Function(Range), + Parameter(Range), + Comment(Range), + Constant(Range), + String(Range), +} + +pub struct Highlighter<'a> { + pub highlights: Vec, + pub parser: &'a Parser, +} + +impl<'a> Highlighter<'a> { + pub fn new(parser: &'a Parser) -> Self { + let mut highlighter = Self { + highlights: Vec::new(), + parser, + }; + + highlighter.visit_parser(parser); + highlighter + } +} + +impl Visitor for Highlighter<'_> { + fn visit_macro(&mut self, macro_exp: &crate::parser::Macro, parser: &Parser) { + self.highlights.push(Highlight::Type( + parser.lexer.cursor_range_to_text_range(¯o_exp.name), + )); + self.highlights.push(Highlight::Keyword( + parser + .lexer + .cursor_range_to_text_range(¯o_exp.macro_pos), + )); + } + + fn visit_macro_end(&mut self, _: &crate::parser::Macro, _parser: &Parser) {} + + fn visit_exec(&mut self, exec_exp: &crate::parser::MadExec, parser: &Parser) { + self.highlights.push(Highlight::Keyword( + parser.lexer.cursor_range_to_text_range(&exec_exp.name), + )); + } + + fn visit_label(&mut self, label: &crate::parser::Label, parser: &Parser) { + self.highlights.push(Highlight::Constant( + parser.lexer.cursor_range_to_text_range(&label.name), + )); + } + + fn visit_if(&mut self, if_exp: &crate::parser::If, parser: &Parser) { + self.highlights.push(Highlight::Keyword( + parser.lexer.cursor_range_to_text_range(&if_exp.if_pos), + )); + } + + fn visit_if_end(&mut self, _if_exp: &crate::parser::If, _parser: &Parser) {} + + fn visit_generic(&mut self, generic: &crate::parser::MadGeneric, parser: &Parser) { + self.highlights.push(Highlight::Function( + parser.lexer.cursor_range_to_text_range(&generic.name), + )); + } + + fn visit_call(&mut self, call_exp: &crate::parser::MadCall, parser: &Parser) { + self.highlights.push(Highlight::Function( + parser.lexer.cursor_range_to_text_range(&call_exp.name), + )); + } + + fn visit_assignment_lhs(&mut self, _lhs: &crate::parser::Expression, _parser: &Parser) {} + + fn visit_comment(&mut self, comment: &(CursorPosition, CursorPosition), parser: &Parser) { + self.highlights.push(Highlight::Comment( + parser.lexer.cursor_range_to_text_range(comment), + )); + } + + fn visit_string(&mut self, string: &(CursorPosition, CursorPosition), parser: &Parser) { + self.highlights.push(Highlight::String( + parser.lexer.cursor_range_to_text_range(string), + )); + } + + fn visit_subparser(&mut self, _: &str, _: &crate::parser::Parser) {} +} + +impl Display for Highlight { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Highlight::Keyword(range) => write!(f, "hi kw | {}", print_range(range)), + Highlight::Type(range) => write!(f, "hi ty | {}", print_range(range)), + Highlight::Class(range) => write!(f, "hi cl | {}", print_range(range)), + Highlight::Function(range) => write!(f, "hi fn | {}", print_range(range)), + Highlight::Parameter(range) => write!(f, "hi pa | {}", print_range(range)), + Highlight::Comment(range) => write!(f, "hi // | {}", print_range(range)), + Highlight::Constant(range) => write!(f, "hi co | {}", print_range(range)), + Highlight::String(range) => write!(f, "hi st | {}", print_range(range)), + } + } +} diff --git a/src/lexer/cursor.rs b/src/lexer/cursor.rs index 7c378ae..3a41ce8 100644 --- a/src/lexer/cursor.rs +++ b/src/lexer/cursor.rs @@ -1,5 +1,7 @@ -use std::{ops::{AddAssign, Add, SubAssign}, fmt::Display}; - +use std::{ + fmt::Display, + ops::{Add, AddAssign, Sub, SubAssign}, +}; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct CursorPosition { @@ -36,6 +38,17 @@ impl Add for CursorPosition { } } +impl Sub for CursorPosition { + type Output = Self; + + fn sub(self, rhs: usize) -> Self::Output { + Self { + absolute: self.absolute - rhs, + line: self.line, + } + } +} + impl Add for &CursorPosition { type Output = CursorPosition; @@ -61,10 +74,7 @@ impl Ord for CursorPosition { impl CursorPosition { pub fn new(absolute: usize, line: usize) -> Self { - Self { - absolute, - line - } + Self { absolute, line } } pub fn advance_line(&mut self) { @@ -86,12 +96,16 @@ impl CursorPosition { impl Default for CursorPosition { fn default() -> Self { - Self{ + Self { absolute: 0, - line: 0 + line: 0, } } } - - +pub fn print_range(range: &tower_lsp::lsp_types::Range) -> String { + format!( + "({}, {}) -- ({}, {})", + range.start.line, range.start.character, range.end.line, range.end.character, + ) +} diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 17ef187..3119d73 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -11,6 +11,11 @@ use crate::error::UTF8_PARSER_MSG; pub trait HasRange { fn get_range(&self) -> (CursorPosition, CursorPosition); + + fn get_lines(&self) -> impl Iterator { + let range = self.get_range(); + range.0.line()..=range.1.line() + } } impl HasRange for (CursorPosition, CursorPosition) { @@ -84,12 +89,21 @@ impl Lexer { } pub fn cursor_pos_to_text_pos(&self, pos: CursorPosition) -> Position { - Position::new(pos.line() as u32, pos.character(&self.lines) as u32) + Position::new(pos.line() as u32 + 1, pos.character(&self.lines) as u32) + } + pub fn cursor_range_to_text_range( + &self, + has_range: &R, + ) -> tower_lsp::lsp_types::Range { + let range = has_range.get_range(); + tower_lsp::lsp_types::Range { + start: self.cursor_pos_to_text_pos(range.0), + end: self.cursor_pos_to_text_pos(range.1), + } } /// advancing the CursorPosition `cursor` by `by` characters, taking into account line breaks pub fn advance_cursor(&self, cursor: &mut CursorPosition, by: usize) { - let by_rest = by; *cursor += by; while self.lines[cursor.line()] < cursor.absolute() { cursor.advance_line() @@ -114,7 +128,7 @@ impl Lexer { self.get_range_bytes(token) } - pub fn get_range_str(&self, token: &R) -> Cow { + pub fn get_range_str(&'_ self, token: &R) -> Cow<'_, str> { String::from_utf8_lossy(self.get_range_bytes(token)) } @@ -229,14 +243,6 @@ impl Lexer { } /// ---- internal reading functions ------------------------------------------------------------ - fn next_char(&mut self) -> Option { - if self.position.absolute() >= self.buffer.len() { - return None; - } - let c = self.buffer[self.position.absolute()]; - self.position += 1; // position is now one character ahead - Some(c) - } fn peak_char(&self) -> Option { if self.position.absolute() >= self.buffer.len() { diff --git a/src/lexer/token.rs b/src/lexer/token.rs index cae8d7a..66dd072 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -1,5 +1,3 @@ -use std::fmt::Display; - use super::{CursorPosition, HasRange}; #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/src/lib.rs b/src/lib.rs index e7e0761..895e8ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ pub mod document; pub mod error; +pub mod highlighter; pub mod lexer; +pub mod measures; pub mod parser; pub mod rules; pub mod semantic_tokens; diff --git a/src/measures/code_blocks.rs b/src/measures/code_blocks.rs new file mode 100644 index 0000000..eff5dae --- /dev/null +++ b/src/measures/code_blocks.rs @@ -0,0 +1,60 @@ +use crate::{lexer::CursorPosition, parser::Parser, visitor::Visitor}; + +pub struct CodeBlocks { + pub macros: usize, + pub statements: usize, + pub elements: usize, +} + +impl CodeBlocks { + pub fn new(parser: &Parser) -> Self { + let mut code_blocks = Self { + macros: 0, + statements: 0, + elements: 0, + }; + code_blocks.visit_parser(parser); + code_blocks + } +} + +impl Visitor for CodeBlocks { + fn visit_macro(&mut self, _macro_exp: &crate::parser::Macro, _parser: &Parser) { + self.macros += 1; + self.statements += 1; + } + + fn visit_macro_end(&mut self, _macro_exp: &crate::parser::Macro, _parser: &Parser) {} + + fn visit_exec(&mut self, _exec_exp: &crate::parser::MadExec, _parser: &Parser) { + self.statements += 1; + } + + fn visit_label(&mut self, _label: &crate::parser::Label, _parser: &Parser) { + self.statements += 1; + } + + fn visit_if(&mut self, _if_exp: &crate::parser::If, _parser: &Parser) { + self.statements += 1; + } + + fn visit_if_end(&mut self, _if_exp: &crate::parser::If, _parser: &Parser) {} + + fn visit_generic(&mut self, _generic: &crate::parser::MadGeneric, _parser: &Parser) { + self.statements += 1; + } + + fn visit_call(&mut self, _call_exp: &crate::parser::MadCall, _parser: &Parser) { + self.statements += 1; + } + + fn visit_assignment_lhs(&mut self, _lhs: &crate::parser::Expression, _parser: &Parser) { + self.statements += 1; + } + + fn visit_comment(&mut self, _comment: &(CursorPosition, CursorPosition), _parser: &Parser) {} + + fn visit_string(&mut self, _string: &(CursorPosition, CursorPosition), _parser: &Parser) {} + + fn visit_subparser(&mut self, _: &str, _: &crate::parser::Parser) {} +} diff --git a/src/measures/loc.rs b/src/measures/loc.rs new file mode 100644 index 0000000..81673f8 --- /dev/null +++ b/src/measures/loc.rs @@ -0,0 +1,86 @@ +use std::collections::HashSet; + +use crate::{ + lexer::{CursorPosition, HasRange}, + parser::Parser, + visitor::Visitor, +}; + +pub struct Loc { + pub lines: HashSet, + pub commentlines: HashSet, +} + +impl Loc { + pub fn new(parser: &Parser) -> Self { + let mut loc = Self { + lines: HashSet::new(), + commentlines: HashSet::new(), + }; + loc.visit_parser(parser); + loc + } +} + +impl Visitor for Loc { + fn visit_macro(&mut self, macro_exp: &crate::parser::Macro, _parser: &Parser) { + for line in macro_exp.name.get_lines() { + self.lines.insert(line); + } + self.lines.insert(macro_exp.end.line()); + } + + fn visit_macro_end(&mut self, _: &crate::parser::Macro, _parser: &Parser) {} + + fn visit_exec(&mut self, exec_exp: &crate::parser::MadExec, _parser: &Parser) { + for line in exec_exp.name.get_lines() { + self.lines.insert(line); + } + } + + fn visit_label(&mut self, label: &crate::parser::Label, _parser: &Parser) { + for line in label.name.get_lines() { + self.lines.insert(line); + } + } + + fn visit_if(&mut self, if_exp: &crate::parser::If, _parser: &Parser) { + for line in if_exp.if_pos.get_lines() { + self.lines.insert(line); + } + } + + fn visit_if_end(&mut self, _: &crate::parser::If, _parser: &Parser) {} + + fn visit_generic(&mut self, generic: &crate::parser::MadGeneric, _parser: &Parser) { + for line in generic.name.get_lines() { + self.lines.insert(line); + } + } + + fn visit_call(&mut self, call_exp: &crate::parser::MadCall, _parser: &Parser) { + for line in call_exp.name.get_lines() { + self.lines.insert(line); + } + } + + fn visit_assignment_lhs(&mut self, lhs: &crate::parser::Expression, _parser: &Parser) { + for line in lhs.get_lines() { + self.lines.insert(line); + } + } + + fn visit_comment(&mut self, comment: &(CursorPosition, CursorPosition), _parser: &Parser) { + for line in comment.get_lines() { + self.commentlines.insert(line); + } + } + + fn visit_string(&mut self, string: &(CursorPosition, CursorPosition), _parser: &Parser) { + for line in string.get_lines() { + self.lines.insert(line); + } + } + + fn visit_subparser(&mut self, _: &str, _: &crate::parser::Parser) {} +} diff --git a/src/measures/mod.rs b/src/measures/mod.rs new file mode 100644 index 0000000..f11c810 --- /dev/null +++ b/src/measures/mod.rs @@ -0,0 +1,2 @@ +pub mod code_blocks; +pub mod loc; diff --git a/src/parser/assignment.rs b/src/parser/assignment.rs index 3791147..de4f2b4 100644 --- a/src/parser/assignment.rs +++ b/src/parser/assignment.rs @@ -1,6 +1,6 @@ -use crate::lexer::{Token, HasRange, CursorPosition}; +use crate::lexer::HasRange; -use super::{Expression, Parser, Problem}; +use super::{Expression, Parser}; #[derive(Debug, PartialEq)] pub struct Assignment { @@ -10,7 +10,6 @@ pub struct Assignment { impl Assignment { pub fn parse(parser: &mut Parser) -> Option { - if let Some(expr) = Expression::parse(parser) { if let Some(token) = parser.peek_token() { if !token.is_assignment() { @@ -24,8 +23,7 @@ impl Assignment { lhs: Box::new(expr), rhs: Some(Box::new(right)), })); - } - else { + } else { return Some(Expression::Assignment(Self { lhs: Box::new(expr), rhs: None, @@ -37,33 +35,24 @@ impl Assignment { None } - pub(crate) fn get_label<'a>(&'a self, pos: &CursorPosition, parser: &'a Parser) -> Option<&[u8]> { - if let Some(rhs) = &self.rhs { - return rhs.get_label(pos, parser); - } - None - } - - pub(crate) fn get_problems(&self, problems: &mut Vec) { - if let Some(e) = &self.rhs { - e.get_problems(problems); - } - } - - pub(crate) fn accept(&self, visitor: &mut V) { - self.lhs.accept(visitor); + pub(crate) fn accept( + &self, + visitor: &mut V, + parser: &crate::parser::Parser, + ) { + visitor.visit_assignment_lhs(self.lhs.as_ref(), parser); + self.lhs.accept(visitor, parser); if let Some(rhs) = &self.rhs { - rhs.accept(visitor); + rhs.accept(visitor, parser); } } } -impl HasRange for Assignment{ +impl HasRange for Assignment { fn get_range(&self) -> (crate::lexer::CursorPosition, crate::lexer::CursorPosition) { if let Some(rhs) = self.rhs.as_ref() { (self.lhs.get_range().0, rhs.get_range().1) - } - else { + } else { self.lhs.get_range() } } @@ -81,7 +70,10 @@ mod tests { if let Expression::Assignment(assignment) = &parser.get_elements()[0] { assert_eq!(parser.get_element_bytes(assignment), b"a = 1"); assert_eq!(parser.get_element_bytes(&*assignment.lhs), b"a"); - assert_eq!(parser.get_element_bytes(&**assignment.rhs.as_ref().unwrap()), b"1"); + assert_eq!( + parser.get_element_bytes(&**assignment.rhs.as_ref().unwrap()), + b"1" + ); } } } diff --git a/src/parser/expression.rs b/src/parser/expression.rs index 4c77cd4..f311318 100644 --- a/src/parser/expression.rs +++ b/src/parser/expression.rs @@ -1,17 +1,9 @@ -use std::{collections::HashMap, fmt::Display}; - -use once_cell::sync::Lazy; -use tower_lsp::lsp_types::{CompletionItem, SemanticToken}; - use crate::{ lexer::{CursorPosition, HasRange, Token}, - semantic_tokens::get_range_token, + parser::MadCall, }; -use super::{ - insert_generic_builder, Assignment, Environment, If, Label, Macro, MadExec, MadGeneric, - MadGenericBuilder, Parser, Problem, GENERIC_BUILTINS, -}; +use super::{Assignment, Environment, If, Label, Macro, MadExec, MadGeneric, Parser}; #[derive(Debug, PartialEq)] pub enum Expression { Label(Label), @@ -26,6 +18,7 @@ pub enum Expression { Exit(Exit), Operator(Operator), Exec(MadExec), + Call(MadCall), Noop(CursorPosition), TokenExp(Token), // debug, todo: remove } @@ -47,6 +40,7 @@ impl HasRange for Expression { Expression::Exit(exit) => (exit.start, exit.end), Expression::Exec(exec) => exec.get_range(), Expression::Noop(pos) => (*pos, *pos), + Expression::Call(call) => call.get_range(), } } } @@ -59,18 +53,24 @@ impl Expression { if let Some(string) = Self::parse_string(parser) { return Some(string); } + if let Some(comment) = Self::parse_comment(parser) { + return Some(comment); + } if let Some(label) = Label::parse(parser) { return Some(Expression::Label(label)); } if let Some(env) = Environment::parse(parser) { return Some(Expression::MadEnvironment(env)); } - if let Some(generic) = MadGeneric::parse(parser) { - return Some(Expression::MadGeneric(generic)); - } if let Some(exec) = MadExec::parse(parser) { return Some(Expression::Exec(exec)); } + if let Some(call) = MadCall::parse(parser) { + return Some(Expression::Call(call)); + } + if let Some(generic) = MadGeneric::parse(parser) { + return Some(Expression::MadGeneric(generic)); + } if let Some(if_object) = If::parse(parser) { return Some(Expression::If(if_object)); } @@ -85,35 +85,23 @@ impl Expression { None } - pub fn accept(&self, visitor: &mut V) { - match self { - Expression::Macro(m) => m.accept(visitor), - Expression::Assignment(a) => a.accept(visitor), - Expression::MadGeneric(g) => g.accept(visitor), - Expression::MadEnvironment(e) => e.accept(visitor), - Expression::Exec(e) => e.accept(visitor), - Expression::If(i) => i.accept(visitor), - Expression::Label(l) => l.accept(visitor), - _ => {}, - } - } - - pub fn get_problems(&self, problems: &mut Vec) { + pub fn accept( + &self, + visitor: &mut V, + parser: &crate::parser::Parser, + ) { match self { - Expression::Label(_) => {} - Expression::Macro(m) => m.get_problems(problems), - Expression::Assignment(a) => a.get_problems(problems), - Expression::String(_) => {} - Expression::Comment(_) => {} - Expression::Symbol(_) => {} - Expression::MadGeneric(g) => g.get_problems(problems), - Expression::MadEnvironment(e) => e.get_problems(problems), - Expression::Exit(_) => {} - Expression::Operator(_) => {} - Expression::Exec(e) => e.get_problems(problems), - Expression::TokenExp(_) => {} - Expression::If(_) => {} - Expression::Noop(cursor_position) => {} + Expression::Macro(m) => m.accept(visitor, parser), + Expression::Assignment(a) => a.accept(visitor, parser), + Expression::MadGeneric(g) => g.accept(visitor, parser), + Expression::MadEnvironment(e) => e.accept(visitor, parser), + Expression::Exec(e) => e.accept(visitor, parser), + Expression::If(i) => i.accept(visitor, parser), + Expression::Label(l) => l.accept(visitor, parser), + Expression::Call(c) => c.accept(visitor, parser), + Expression::Comment(c) => visitor.visit_comment(c, parser), + Expression::String(s) => visitor.visit_string(s, parser), + _ => {} } } @@ -139,122 +127,32 @@ impl Expression { None } - /// returns the label of the element under cursor, this is to find the definition and, - /// possibly, jump to it - pub fn get_label<'a>(&'a self, pos: &CursorPosition, parser: &'a Parser) -> Option<&[u8]> { - match self { - Expression::Label(_) => None, - Expression::Macro(_) => None, - Expression::Assignment(a) => a.get_label(pos, parser), - Expression::String(_) => None, - Expression::Comment(_) => None, - Expression::Symbol(s) => Some(s.as_bytes()), - Expression::MadGeneric(m) => m.get_label(pos, parser), - Expression::MadEnvironment(m) => m.get_label(pos, parser), - Expression::Exit(_) => None, - Expression::Operator(_) => None, - Expression::Exec(s) => s.get_label(pos, parser), - Expression::TokenExp(t) => { - let range = t.get_range(); - if &range.0 < pos && pos < &range.1 { - Some(parser.get_element_bytes(&range)) - } else { - None - } - } - Expression::If(_) => None, - Expression::Noop(cursor_position) => None, - } - } + fn parse_comment(parser: &mut Parser) -> Option { + let mut comments = Vec::new(); - pub fn get_completion(&self, pos: &CursorPosition, items: &mut Vec) { - match self { - Expression::Label(_) => {} - Expression::Macro(m) => m.get_completion(pos, items), - Expression::Assignment(_) => {} - Expression::String(_) => {} - Expression::Comment(_) => {} - Expression::Symbol(_) => {} - Expression::MadGeneric(g) => g.get_completion(pos, items), - Expression::MadEnvironment(e) => e.get_completion(pos, items), - Expression::Exit(_) => {} - Expression::Exec(_) => {} - Expression::Operator(_) => {} - Expression::TokenExp(_) => {} - Expression::If(_) => {} - Expression::Noop(cursor_position) => {} - } - } - pub fn to_semantic_token( - &self, - semantic_tokens: &mut Vec, - pre_line: &mut u32, - pre_start: &mut u32, - parser: &Parser, - ) { - match self { - Self::String(range) => { - semantic_tokens.push(get_range_token(range, 0, pre_line, pre_start, parser)) - } - Self::TokenExp(Token::Comment(range)) => { - semantic_tokens.push(get_range_token(range, 2, pre_line, pre_start, parser)) - } - Self::TokenExp(Token::MultilineComment(lines)) => { - for range in lines.iter() { - semantic_tokens.push(get_range_token(range, 2, pre_line, pre_start, parser)); + while let Some(token) = parser.peek_token().cloned() { + match token { + Token::Comment(range) => { + parser.advance(); + comments.push(range); } - } - Self::Macro(m) => m.to_semantic_token(semantic_tokens, pre_line, pre_start, parser), - Self::Label(label) => { - semantic_tokens.push(get_range_token(&label.name, 6, pre_line, pre_start, parser)); - label - .command - .to_semantic_token(semantic_tokens, pre_line, pre_start, parser); - } - Self::MadGeneric(mad_generic) => { - mad_generic.to_semantic_token(semantic_tokens, pre_line, pre_start, parser); - } - Self::MadEnvironment(env) => { - env.to_semantic_token(semantic_tokens, pre_line, pre_start, parser); - } - Self::Exit(exit) => { - semantic_tokens.push(get_range_token(exit, 0, pre_line, pre_start, parser)); - - for lines in parser.lexer.lines()[exit.start.line() + 1..].windows(2) { - let length = lines[1] - lines[0]; - semantic_tokens.push(SemanticToken { - delta_line: 1, - delta_start: 0, - length: length as u32, - token_type: 2, - token_modifiers_bitset: 0, - }); + Token::MultilineComment(ranges) => { + parser.advance(); + for range in ranges { + comments.push(range); + } } + _ => break, // Stop when we encounter a non-comment token } - _ => {} } - } - pub(crate) fn get_highlights( - &self, - pos: &CursorPosition, - parser: &Parser, - ) -> Vec<(CursorPosition, CursorPosition)> { - match self { - Expression::Label(_) => vec![], - Expression::Macro(m) => m.get_highlights(pos, parser), - Expression::Assignment(_) => vec![], - Expression::String(_) => vec![], - Expression::Comment(_) => vec![], - Expression::Symbol(_) => vec![], - Expression::MadGeneric(_) => vec![], - Expression::MadEnvironment(_) => vec![], - Expression::Exit(_) => vec![], - Expression::Operator(_) => vec![], - Expression::Exec(_) => vec![], - Expression::TokenExp(_) => vec![], - Expression::If(_) => vec![], - Expression::Noop(_) => vec![], + if comments.is_empty() { + None + } else { + Some(Expression::Comment(( + comments.first().unwrap().0, + comments.last().unwrap().1, + ))) } } } diff --git a/src/parser/label.rs b/src/parser/label.rs index 72116a0..6785108 100644 --- a/src/parser/label.rs +++ b/src/parser/label.rs @@ -33,8 +33,12 @@ impl Label { None } - pub(crate) fn accept(&self, visitor: &mut V) { - visitor.visit_label(self); + pub(crate) fn accept( + &self, + visitor: &mut V, + parser: &crate::parser::Parser, + ) { + visitor.visit_label(self, parser); } } @@ -43,24 +47,3 @@ impl HasRange for Label { (self.name.get_range().0, self.command.get_range().1) } } - -#[cfg(test)] -mod tests { - use crate::parser::Expression; - - use super::*; - - #[test] - fn parse_label() { - let parser = Parser::from_bytes(b"label: twiss, sequence=lhcb;".to_vec(), None); - - if let Expression::Label(label) = &parser.get_elements()[0] { - assert_eq!(parser.get_element_str(&label.name), "label"); - assert_eq!( - parser.get_element_str(&label.command), - "twiss, sequence=lhcb" - ); - assert_eq!(parser.labels.keys().collect::>(), vec![b"label"]); - } - } -} diff --git a/src/parser/madcall.rs b/src/parser/madcall.rs new file mode 100644 index 0000000..38d0403 --- /dev/null +++ b/src/parser/madcall.rs @@ -0,0 +1,112 @@ +use super::Expression; +use crate::lexer::{CursorPosition, HasRange, Token}; + +#[derive(Debug, PartialEq, Default)] +pub struct MadCall { + pub name: Token, // usually "call" + pub file_kw: Token, // usually "file" + pub eq: Option, // '=' token, optional for robustness + pub filename: Option>, // the filename expression (can be string, ident, etc.) + pub range: (CursorPosition, CursorPosition), +} + +impl MadCall { + pub(crate) fn parse(parser: &mut super::Parser) -> Option { + let start = parser + .peek_token() + .map(|t| t.get_range().0) + .unwrap_or_default(); + + // Expect "call" + let name = parser.peek_token()?.clone(); + if !parser.lexer.compare_range(&name, b"call") { + return None; + } + parser.advance(); + + // Expect comma + if let Some(Token::Komma(_)) = parser.peek_token() { + parser.advance(); + } else { + return Some(MadCall { + name, + file_kw: Token::default(), + eq: None, + filename: None, + range: (start, start), + }); + } + + // Expect "file" + let file_kw = parser.peek_token()?.clone(); + let range_end = file_kw.get_range().1; + if !parser.lexer.compare_range(&file_kw, b"file") { + return Some(MadCall { + name, + file_kw, + eq: None, + filename: None, + range: (start, range_end), + }); + } + parser.advance(); + + // Optional '=' + let mut eq = None; + if let Some(Token::Equal(_)) = parser.peek_token() { + eq = parser.peek_token().cloned(); + parser.advance(); + } + + // Parse filename expression + let filename = if let Some(expr) = Expression::parse(parser) { + Some(Box::new(expr)) + } else { + None + }; + + // End position + let end = filename + .as_ref() + .map(|e| e.get_range().1) + .or_else(|| eq.as_ref().map(|t| t.get_range().1)) + .unwrap_or_else(|| file_kw.get_range().1); + + Some(MadCall { + name, + file_kw, + eq, + filename, + range: (start, end), + }) + } + + pub(crate) fn accept( + &self, + visitor: &mut V, + parser: &crate::parser::Parser, + ) { + visitor.visit_call(self, parser); + if let Some(filename) = &self.filename { + filename.accept(visitor, parser); + + if let Some(filename) = self.get_filename(parser) { + visitor.visit_subparser(&filename, parser); + } + } + } + + pub fn get_filename(&self, parser: &crate::parser::Parser) -> Option { + match self.filename.as_ref()?.as_ref() { + Expression::String(s) => Some(parser.get_element_str(&(s.0 + 1, s.1))), + Expression::TokenExp(t) => Some(parser.get_element_str(t)), + _ => None, + } + } +} + +impl HasRange for MadCall { + fn get_range(&self) -> (CursorPosition, CursorPosition) { + self.range + } +} diff --git a/src/parser/madenvironment.rs b/src/parser/madenvironment.rs index 18c7a31..dbe44a0 100644 --- a/src/parser/madenvironment.rs +++ b/src/parser/madenvironment.rs @@ -1,17 +1,10 @@ use std::collections::HashMap; use once_cell::sync::Lazy; -use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind}; -use crate::{ - error::UTF8_PARSER_MSG, - lexer::{CursorPosition, HasRange, Token}, - semantic_tokens::get_range_token, -}; +use crate::lexer::{HasRange, Token}; -use super::{ - insert_generic_builder, Expression, MadGenericBuilder, MadParam, MatchParam, Parser, Problem, -}; +use super::{insert_generic_builder, Expression, MadGenericBuilder, MadParam, MatchParam, Parser}; pub const GENERIC_ENVS: Lazy> = Lazy::new(|| { let mut envs = HashMap::new(); @@ -263,78 +256,13 @@ impl Environment { None } - pub fn get_completion(&self, pos: &CursorPosition, items: &mut Vec) { - if &self.start.get_range().0 < pos && &self.end.get_range().1 > pos { - for expr in self.expressions.iter() { - expr.get_completion(pos, items); - } - if let Some(builder) = &GENERIC_ENVS.get(self.match_start) { - for name in builder.generic_builders.keys() { - items.push(CompletionItem { - label: String::from_utf8(name.to_vec()) - .unwrap_or_else(|_| UTF8_PARSER_MSG.to_string()), - kind: Some(CompletionItemKind::FUNCTION), - ..Default::default() - }); - } - } - } - } - - pub fn to_semantic_token( + pub(crate) fn accept( &self, - semantic_tokens: &mut Vec, - pre_line: &mut u32, - pre_start: &mut u32, - parser: &Parser, + visitor: &mut V, + parser: &crate::parser::Parser, ) { - semantic_tokens.push(get_range_token( - &self.start.get_range(), - 7, - pre_line, - pre_start, - parser, - )); - - MadParam::to_semantic_token(&self.args, semantic_tokens, pre_line, pre_start, parser); - for expr in self.expressions.iter() { - expr.to_semantic_token(semantic_tokens, pre_line, pre_start, parser); - } - - semantic_tokens.push(get_range_token( - &self.end.get_range(), - 7, - pre_line, - pre_start, - parser, - )); - } - - pub(crate) fn get_label<'a>( - &'a self, - pos: &CursorPosition, - parser: &'a Parser, - ) -> Option<&[u8]> { - let range = self.start.get_range(); - if &range.0 < pos && pos < &range.1 { - return Some(parser.get_element_bytes(&range)); - } - None - } - - pub(crate) fn get_problems(&self, problems: &mut Vec) { - log::debug!( - "forwarding problems for {} expressions", - self.expressions.len() - ); - for e in self.expressions.iter() { - e.get_problems(problems); - } - } - - pub(crate) fn accept(&self, visitor: &mut V) { for e in self.expressions.iter() { - e.accept(visitor); + e.accept(visitor, parser); } } } @@ -436,13 +364,6 @@ mod tests { assert_eq!(parser.get_element_str(&env.start), "seqedit"); assert_eq!(parser.get_element_str(&env.expressions[1]), "flatten"); assert_eq!(parser.get_element_str(&env.end), ";"); - - let mut st = Vec::new(); - let mut pre_line = 0; - let mut pre_start = 0; - env.to_semantic_token(&mut st, &mut pre_line, &mut pre_start, &parser); - - //assert!(false, "{:#?}\n{:#?}", env.expressions, st); } else { assert!(false, "should be an env"); } diff --git a/src/parser/madexec.rs b/src/parser/madexec.rs index becc951..0415500 100644 --- a/src/parser/madexec.rs +++ b/src/parser/madexec.rs @@ -1,10 +1,10 @@ use crate::lexer::{CursorPosition, HasRange, Token}; -use super::{Expression, Macro, Problem}; +use super::Macro; #[derive(Debug, Eq, PartialEq, Clone, Default)] pub struct MadExec { - name: Token, + pub name: Token, callee: Token, parenopen: CursorPosition, args: Vec, @@ -12,10 +12,6 @@ pub struct MadExec { } impl MadExec { - pub(crate) fn get_problems(&self, problems: &mut Vec) { - problems.push(Problem::MissingCallee(vec![], self.callee.get_range())); - } - pub(crate) fn parse(parser: &mut super::Parser) -> Option { if let Some(token) = parser.peek_token() { if parser.lexer.compare_range(token, b"exec") { @@ -65,8 +61,12 @@ impl MadExec { self.callee.get_range() } - pub(crate) fn accept(&self, visitor: &mut V) { - visitor.visit_exec(self); + pub(crate) fn accept( + &self, + visitor: &mut V, + parser: &crate::parser::Parser, + ) { + visitor.visit_exec(self, parser); } } diff --git a/src/parser/madgeneric.rs b/src/parser/madgeneric.rs index e5d6e11..0733e4d 100644 --- a/src/parser/madgeneric.rs +++ b/src/parser/madgeneric.rs @@ -1,11 +1,10 @@ use std::collections::HashMap; use once_cell::sync::Lazy; -use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind}; -use crate::{lexer::{Token, CursorPosition, HasRange}, semantic_tokens::{get_range_token}, error::UTF8_PARSER_MSG}; +use crate::lexer::{CursorPosition, HasRange, Token}; -use super::{Expression, Parser, Problem}; +use super::{Expression, Parser}; pub type MatchParam = (Vec, Vec>); @@ -13,16 +12,62 @@ pub type MatchParam = (Vec, Vec>); pub const GENERIC_BUILTINS: Lazy> = Lazy::new(|| { let mut builtins = HashMap::new(); - insert_generic_builder(&mut builtins, b"option", &["echo", "warn", "verbose", "debug", "echomacro", - "trace", "verify", "tell", "reset", "no_fatal_stop", "keep_exp_move", "rbarc", "thin_foc", "bborbit", "sympl", - "twiss_print", "threader"]); + insert_generic_builder( + &mut builtins, + b"option", + &[ + "echo", + "warn", + "verbose", + "debug", + "echomacro", + "trace", + "verify", + "tell", + "reset", + "no_fatal_stop", + "keep_exp_move", + "rbarc", + "thin_foc", + "bborbit", + "sympl", + "twiss_print", + "threader", + ], + ); insert_generic_builder(&mut builtins, b"set", &["format", "sequence"]); - insert_generic_builder(&mut builtins, b"use", &["sequence", "period", "survey", "range"]); - insert_generic_builder(&mut builtins, b"select", &["flag", "range", "class", "pattern", "sequence", - "full", "clear", "column", "slice", "thick", "step", "at", "seqedit", "error", "makethin", - "sectormap", "save", "interpolate", "twiss"]); + insert_generic_builder(&mut builtins, b"value", &[]); + insert_generic_builder( + &mut builtins, + b"use", + &["sequence", "period", "survey", "range"], + ); + insert_generic_builder( + &mut builtins, + b"select", + &[ + "flag", + "range", + "class", + "pattern", + "sequence", + "full", + "clear", + "column", + "slice", + "thick", + "step", + "at", + "seqedit", + "error", + "makethin", + "sectormap", + "save", + "interpolate", + "twiss", + ], + ); insert_generic_builder(&mut builtins, b"assign", &["echo", "truncate"]); - insert_generic_builder(&mut builtins, b"call", &["file"]); insert_generic_builder(&mut builtins, b"print", &["text"]); insert_generic_builder(&mut builtins, b"printf", &["text", "value"]); insert_generic_builder(&mut builtins, b"renamefile", &["file", "to"]); @@ -30,60 +75,219 @@ pub const GENERIC_BUILTINS: Lazy> = La insert_generic_builder(&mut builtins, b"create", &["table", "column"]); insert_generic_builder(&mut builtins, b"delete", &["table", "sequence"]); insert_generic_builder(&mut builtins, b"readmytable", &["table", "file"]); - insert_generic_builder(&mut builtins, b"twiss", &["sequence", "line", "range", - "deltap", "chrom", "centre", "tolerance", "file", "table", "notable", - "rmatrix", "sectormap", "sectortable", "sectorfile", "sectorpure", - "eigenvector", "eigenfile", "keeporbit", "useorbit", "couple", "exact", - "ripken", "tapering"]); + insert_generic_builder( + &mut builtins, + b"twiss", + &[ + "sequence", + "line", + "range", + "deltap", + "chrom", + "centre", + "tolerance", + "file", + "table", + "notable", + "rmatrix", + "sectormap", + "sectortable", + "sectorfile", + "sectorpure", + "eigenvector", + "eigenfile", + "keeporbit", + "useorbit", + "couple", + "exact", + "ripken", + "tapering", + ], + ); insert_generic_builder(&mut builtins, b"fill", &["table", "row"]); - insert_generic_builder(&mut builtins, b"setvars", &["table", "row", "knob", "const", "noappend"]); - insert_generic_builder(&mut builtins, b"fill_knob", &["table", "row", "knob", "scale"]); - insert_generic_builder(&mut builtins, b"setvars_lin", &["table", "row1", "row2", "param"]); - - insert_generic_builder(&mut builtins, b"beam", &["particle", "mass", "charge", - "energy", "pc", "gamma", "beta", "brho", - "ex", "ey", - "exn", "eyn", - "et", "sigt", "sigt", - "kbunch", "npart", "bcurrent", - "bunched", "radiate", "bv", - "sequence", - "positron", "electron", "proton", "antiproton", "posmuon", "negmuon", "ion"]); + insert_generic_builder( + &mut builtins, + b"setvars", + &["table", "row", "knob", "const", "noappend"], + ); + insert_generic_builder( + &mut builtins, + b"fill_knob", + &["table", "row", "knob", "scale"], + ); + insert_generic_builder( + &mut builtins, + b"setvars_lin", + &["table", "row1", "row2", "param"], + ); + + insert_generic_builder( + &mut builtins, + b"beam", + &[ + "particle", + "mass", + "charge", + "energy", + "pc", + "gamma", + "beta", + "brho", + "ex", + "ey", + "exn", + "eyn", + "et", + "sigt", + "sigt", + "kbunch", + "npart", + "bcurrent", + "bunched", + "radiate", + "bv", + "sequence", + "positron", + "electron", + "proton", + "antiproton", + "posmuon", + "negmuon", + "ion", + ], + ); insert_generic_builder(&mut builtins, b"resbeam", &["sequence"]); insert_generic_builder(&mut builtins, b"chdir", &["dir"]); // ---- lattice elements ----------------------------------------------------------------------- - insert_generic_builder(&mut builtins, b"rbend", &["l", "angle", "tilt", - "k0", "k0s", "k1", "k1s", "k2", "k2s", "e1", "e2", "fint", "fintx", - "hgap", "h1", "h2", "thick", "add_angle", "kill_ent_fringe" - ]); - insert_generic_builder(&mut builtins, b"rbend", &["l", "angle", "tilt", - "k0", "k0s", "k1", "k1s", "e1", "e2", "fint", "fintx", - "hgap", "h1", "h2", "thick", "kill_ent_fringe" - ]); + insert_generic_builder( + &mut builtins, + b"rbend", + &[ + "l", + "angle", + "tilt", + "k0", + "k0s", + "k1", + "k1s", + "k2", + "k2s", + "e1", + "e2", + "fint", + "fintx", + "hgap", + "h1", + "h2", + "thick", + "add_angle", + "kill_ent_fringe", + ], + ); + insert_generic_builder( + &mut builtins, + b"rbend", + &[ + "l", + "angle", + "tilt", + "k0", + "k0s", + "k1", + "k1s", + "e1", + "e2", + "fint", + "fintx", + "hgap", + "h1", + "h2", + "thick", + "kill_ent_fringe", + ], + ); insert_generic_builder(&mut builtins, b"drift", &["l"]); - insert_generic_builder(&mut builtins, b"dipedge", &["h", "e1", "fint", "hgap", "tilt"]); - insert_generic_builder(&mut builtins, b"quadrupole", &["l", "k1", "k1s", "tilt", "thick"]); + insert_generic_builder( + &mut builtins, + b"dipedge", + &["h", "e1", "fint", "hgap", "tilt"], + ); + insert_generic_builder( + &mut builtins, + b"quadrupole", + &["l", "k1", "k1s", "tilt", "thick"], + ); insert_generic_builder(&mut builtins, b"sextupole", &["l", "k2", "k2s", "tilt"]); insert_generic_builder(&mut builtins, b"octupole", &["l", "k3", "k3s", "tilt"]); insert_generic_builder(&mut builtins, b"multipole", &["lrad", "knl", "ksl", "tilt"]); insert_generic_builder(&mut builtins, b"solenoid", &["l", "ks", "ksi"]); insert_generic_builder(&mut builtins, b"nllens", &["knll", "kcll"]); - insert_generic_builder(&mut builtins, b"hkicker", &["l", "tilt", "sinkick", "kick", "sintune", "sinpeak", "sinphase"]); - insert_generic_builder(&mut builtins, b"vkicker", &["l", "tilt", "sinkick", "kick", "sintune", "sinpeak", "sinphase"]); + insert_generic_builder( + &mut builtins, + b"hkicker", + &[ + "l", "tilt", "sinkick", "kick", "sintune", "sinpeak", "sinphase", + ], + ); + insert_generic_builder( + &mut builtins, + b"vkicker", + &[ + "l", "tilt", "sinkick", "kick", "sintune", "sinpeak", "sinphase", + ], + ); insert_generic_builder(&mut builtins, b"kicker", &["l", "hkick", "vkick", "tilt"]); - insert_generic_builder(&mut builtins, b"rfcavity", &["l", "volt", "lag", "freq", "harmon", "n_bessel", "no_cavity_totalpath"]); - insert_generic_builder(&mut builtins, b"twcavity", &["l", "volt", "lag", "freq", "psi", "delta_lag"]); - insert_generic_builder(&mut builtins, b"crabcavity", &["l", "volt", "lag", "freq", "harmon", - "rv1", "rv2", "rv3", "rv4", "rph1","rph2", "lagf"]); - insert_generic_builder(&mut builtins, b"hacdipole", &["l", "volt", "lag", "freq", "ramp1","ramp2","ramp3","ramp4", ]); - insert_generic_builder(&mut builtins, b"vacdipole", &["l", "volt", "lag", "freq", "ramp1","ramp2","ramp3","ramp4", ]); - insert_generic_builder(&mut builtins, b"rfmultipole", &["l", "volt", "lag", "freq", "harmon", "lrad", "tilt", - "knl", "ksl", "pnl", "psl"]); + insert_generic_builder( + &mut builtins, + b"rfcavity", + &[ + "l", + "volt", + "lag", + "freq", + "harmon", + "n_bessel", + "no_cavity_totalpath", + ], + ); + insert_generic_builder( + &mut builtins, + b"twcavity", + &["l", "volt", "lag", "freq", "psi", "delta_lag"], + ); + insert_generic_builder( + &mut builtins, + b"crabcavity", + &[ + "l", "volt", "lag", "freq", "harmon", "rv1", "rv2", "rv3", "rv4", "rph1", "rph2", + "lagf", + ], + ); + insert_generic_builder( + &mut builtins, + b"hacdipole", + &[ + "l", "volt", "lag", "freq", "ramp1", "ramp2", "ramp3", "ramp4", + ], + ); + insert_generic_builder( + &mut builtins, + b"vacdipole", + &[ + "l", "volt", "lag", "freq", "ramp1", "ramp2", "ramp3", "ramp4", + ], + ); + insert_generic_builder( + &mut builtins, + b"rfmultipole", + &[ + "l", "volt", "lag", "freq", "harmon", "lrad", "tilt", "knl", "ksl", "pnl", "psl", + ], + ); insert_generic_builder(&mut builtins, b"save", &["file"]); - - builtins + builtins }); // ---- structs ------------------------------------------------------------------------------------ @@ -102,7 +306,7 @@ pub const GENERIC_BUILTINS: Lazy> = La /// The insert_generic method can be used to insert a generic MadX command into a map (via the /// `MadGenericBuilder` helper struct). #[derive(Debug, PartialEq)] -pub struct MadGeneric{ +pub struct MadGeneric { pub match_name: &'static [u8], pub name: Token, pub args: Vec, @@ -116,7 +320,7 @@ pub struct MadGeneric{ /// `-ATTRIBUTE` -> `ATTRIBUTE = false` /// #[derive(Debug, PartialEq)] -pub struct MadParam{ +pub struct MadParam { pub valid: bool, pub sign: Option, pub attribute: Token, @@ -134,62 +338,22 @@ pub struct MadGenericBuilder { impl MadGeneric { pub fn parse(parser: &mut Parser) -> Option { for (_, builder) in GENERIC_BUILTINS.iter() { - if let Some(builtin) = builder.parse(parser){ + if let Some(builtin) = builder.parse(parser) { return Some(builtin); } } None } - pub fn get_completion(&self, pos: &CursorPosition, items: &mut Vec) { - let range = self.get_range(); - if &range.0 < pos && pos < &range.1 { - if let Some(builder) = &GENERIC_BUILTINS.get(self.match_name) { - for (arg, known_flags) in builder.match_params.iter() { - items.push(CompletionItem{ - label: String::from_utf8(arg.to_vec()).unwrap_or_else(|_| UTF8_PARSER_MSG.to_string()), - kind: Some(CompletionItemKind::FIELD), - ..Default::default() - }); - for flag in known_flags.iter() { - items.push(CompletionItem{ - label: String::from_utf8(flag.to_vec()).unwrap_or_else(|_| UTF8_PARSER_MSG.to_string()), - kind: Some(CompletionItemKind::CONSTANT), - ..Default::default() - }); - } - } - } - - } - } - pub fn to_semantic_token(&self, semantic_tokens: &mut Vec, pre_line: &mut u32, pre_start: &mut u32, parser: &Parser) { - if let Token::Ident(range) = self.name {semantic_tokens.push(get_range_token(&range, 4, pre_line, pre_start, parser));} - - MadParam::to_semantic_token(&self.args, semantic_tokens, pre_line, pre_start, parser); - - } - - pub(crate) fn get_label<'a>(&'a self, pos: &CursorPosition, parser: &'a Parser) -> Option<&[u8]> { - for p in self.args.iter() { - if let Some(label) = p.get_label(pos, parser) { return Some(label); } - } - None - } - - pub(crate) fn get_problems(&self, problems: &mut Vec) { - for arg in self.args.iter() { - if !arg.valid { - problems.push(Problem::InvalidParam(arg.get_range())); - } - } - } - - pub(crate) fn accept(&self, visitor: &mut V) { - visitor.visit_generic(self); + pub(crate) fn accept( + &self, + visitor: &mut V, + parser: &crate::parser::Parser, + ) { + visitor.visit_generic(self, parser); for arg in self.args.iter() { if let Some(value) = arg.value.as_ref() { - value.accept(visitor); + value.accept(visitor, parser); } } } @@ -198,7 +362,7 @@ impl MadGeneric { impl HasRange for MadGeneric { fn get_range(&self) -> (CursorPosition, CursorPosition) { let r1 = self.name.get_range(); - if let Some(last) = self.args.last(){ + if let Some(last) = self.args.last() { let r2 = last.get_range(); return (r1.0, r2.1); } @@ -210,7 +374,7 @@ impl HasRange for MadGeneric { impl MadParam { pub fn parse(parser: &mut Parser) -> Option { if let Some(token) = parser.peek_token() { - let mut param = Self{ + let mut param = Self { valid: false, sign: None, attribute: Default::default(), @@ -228,7 +392,7 @@ impl MadParam { } if let Some(Token::Equal(_)) = parser.peek_token() { parser.advance(); - + let last_pos = parser.get_position(); // todo: missing test for syntax error if let Some(expr) = Expression::parse(parser) { @@ -238,14 +402,14 @@ impl MadParam { let token = parser.next_token().unwrap(); // we know that here's a // valid token Some(Box::new(Expression::TokenExp(token.clone()))) - }, + } Expression::MadEnvironment(_) => { parser.set_position(last_pos); let token = parser.next_token().unwrap(); // we know that here's a // valid token Some(Box::new(Expression::TokenExp(token.clone()))) - }, - _ => Some(Box::new(expr)) + } + _ => Some(Box::new(expr)), }; } } @@ -256,17 +420,6 @@ impl MadParam { None } - pub fn to_semantic_token(args: &[Self], semantic_tokens: &mut Vec, pre_line: &mut u32, pre_start: &mut u32, parser: &Parser) { - for arg in args.iter() { - if !arg.valid {continue;} - let range = arg.attribute.get_range(); - semantic_tokens.push(get_range_token(&arg.attribute, 5, pre_line, pre_start, parser)); - if let Some(value) = &arg.value { - value.to_semantic_token(semantic_tokens, pre_line, pre_start, parser); - } - } - } - pub fn get_range(&self) -> (CursorPosition, CursorPosition) { let start = if let Some(sign) = &self.sign { sign.get_range().0 @@ -291,7 +444,10 @@ impl MadParam { parser.advance(); //let param = MadParam::parse(parser)?; if let Some(mut param) = MadParam::parse(parser) { - let bytes = &parser.get_element_bytes(¶m.attribute).to_ascii_lowercase().to_vec(); + let bytes = &parser + .get_element_bytes(¶m.attribute) + .to_ascii_lowercase() + .to_vec(); for (p, _) in match_params.iter() { if p == bytes { param.valid = true; @@ -299,29 +455,23 @@ impl MadParam { } } args.push(param); - } - else { + } else { break; } - } - else { + } else { // this is actually an error state, but we continue for the moment parser.advance(); } } args } - - fn get_label<'a>(&'a self, pos: &CursorPosition, parser: &'a Parser) -> Option<&[u8]> { - self.value.as_ref()?.get_label(pos, parser) - } } // ---- MadGenericBuilder -------------------------------------------------------------------------- impl MadGenericBuilder { pub fn parse(&self, parser: &mut Parser) -> Option { if let Some(Token::Ident(name)) = parser.peek_token().cloned() { - if !parser.lexer.compare_range(&name, self.match_name){ + if !parser.lexer.compare_range(&name, self.match_name) { return None; } parser.advance(); @@ -344,73 +494,75 @@ impl MadGenericBuilder { //} } -pub fn insert_generic_builder(map: &mut HashMap<&'static [u8], MadGenericBuilder>, - match_name: &'static [u8], - match_params: &[&str]) { +pub fn insert_generic_builder( + map: &mut HashMap<&'static [u8], MadGenericBuilder>, + match_name: &'static [u8], + match_params: &[&str], +) { map.insert( match_name, MadGenericBuilder { match_name, - match_params: match_params.iter() - .map(|s| - (s.as_bytes().to_vec(), - vec![]) - ).collect(), - } - ); + match_params: match_params + .iter() + .map(|s| (s.as_bytes().to_vec(), vec![])) + .collect(), + }, + ); } /// Inserts a new generic builder with flags with known values, e.g. SELECT. -pub fn insert_generic_builder_known_flags(map: &mut HashMap<&'static [u8], MadGenericBuilder>, - match_name: &'static [u8], - match_params: &[&str], - known_flags: &[&[&str]]) { +pub fn insert_generic_builder_known_flags( + map: &mut HashMap<&'static [u8], MadGenericBuilder>, + match_name: &'static [u8], + match_params: &[&str], + known_flags: &[&[&str]], +) { map.insert( match_name, MadGenericBuilder { match_name, - match_params: match_params.iter() - .zip(known_flags).map(|(s, f)| ( - s.as_bytes().to_vec(), - f.iter().map(|flag| flag.as_bytes().to_vec()).collect() + match_params: match_params + .iter() + .zip(known_flags) + .map(|(s, f)| { + ( + s.as_bytes().to_vec(), + f.iter().map(|flag| flag.as_bytes().to_vec()).collect(), ) - ).collect(), - } - ); + }) + .collect(), + }, + ); } - - #[cfg(test)] mod tests { - use crate::parser::{Parser, Expression}; + use crate::parser::{Expression, Parser}; - #[test] - pub fn incomplete() { - let parser = Parser::from_str("call, fi"); + //#[test] + //pub fn incomplete() { + // let parser = Parser::from_str("call, fi"); - let call = &parser.get_elements()[0]; + // let call = &parser.get_elements()[0]; - if let Expression::MadGeneric(g) = call { - assert_eq!(parser.get_element_str(&g.name), "call"); - } - else { - assert!(false, "this should be recognized as incomplete CALL"); - } - } + // if let Expression::MadGeneric(g) = call { + // assert_eq!(parser.get_element_str(&g.name), "call"); + // } else { + // assert!(false, "this should be recognized as incomplete CALL"); + // } + //} - #[test] - pub fn incomplete_inside() { - let parser = Parser::from_str("call, fi\ntwiss, sequence=lhcb1;"); + //#[test] + //pub fn incomplete_inside() { + // let parser = Parser::from_str("call, fi\ntwiss, sequence=lhcb1;"); - let call = &parser.get_elements()[0]; + // let call = &parser.get_elements()[0]; - if let Expression::MadGeneric(g) = call { - assert_eq!(parser.get_element_str(&g.name), "call"); - } - else { - assert!(false, "this should be recognized as incomplete CALL"); - } - } + // if let Expression::MadGeneric(g) = call { + // assert_eq!(parser.get_element_str(&g.name), "call"); + // } else { + // assert!(false, "this should be recognized as incomplete CALL"); + // } + //} } - diff --git a/src/parser/madif.rs b/src/parser/madif.rs index 16e806d..abd0b15 100644 --- a/src/parser/madif.rs +++ b/src/parser/madif.rs @@ -5,6 +5,7 @@ use crate::{ #[derive(Debug, PartialEq, Default)] pub struct If { + pub if_pos: Token, pub parenopen: CursorPosition, pub parenclose: CursorPosition, pub condition: Vec, // should be only one @@ -27,8 +28,10 @@ impl If { } pub fn parse_inner(parser: &mut Parser) -> Option { + let mut if_object = If::default(); if let Some(Token::Ident(if_keyword)) = parser.peek_token() { if parser.lexer.compare_range(if_keyword, b"if") { + if_object.if_pos = Token::Ident(if_keyword.clone()); parser.advance(); } else { return None; @@ -37,8 +40,6 @@ impl If { return None; } - let mut if_object = If::default(); - if let Some(Token::ParentOpen(pos)) = parser.next_token() { if_object.parenopen = pos.clone(); } else { @@ -69,15 +70,20 @@ impl If { Some(if_object) } - pub(crate) fn accept(&self, visitor: &mut V) { - visitor.visit_if(self); + pub(crate) fn accept( + &self, + visitor: &mut V, + parser: &crate::parser::Parser, + ) { + visitor.visit_if(self, parser); - for expr in self.condition.iter() { - expr.accept(visitor); - } + //for expr in self.condition.iter() { + // expr.accept(visitor, parser); + //} for expr in self.body.iter() { - expr.accept(visitor); + expr.accept(visitor, parser); } + visitor.visit_if_end(self, parser); } } diff --git a/src/parser/madmacro.rs b/src/parser/madmacro.rs index b9e5287..ad6e002 100644 --- a/src/parser/madmacro.rs +++ b/src/parser/madmacro.rs @@ -1,8 +1,6 @@ -use tower_lsp::lsp_types::{CompletionItem, SemanticToken}; +use crate::lexer::{CursorPosition, HasRange, Token}; -use crate::{lexer::{Token, CursorPosition, HasRange}, semantic_tokens::get_range_token}; - -use super::{Expression, Parser, Assignment, Problem}; +use super::{Assignment, Expression, Parser}; #[derive(Debug, PartialEq, Default)] pub struct Macro { @@ -15,8 +13,6 @@ pub struct Macro { pub end: CursorPosition, } - - impl Macro { pub fn parse(parser: &mut Parser) -> Option { let before = parser.get_position(); @@ -41,16 +37,13 @@ impl Macro { m.parenopen = parenopen; m.args = tokens; m.parenclose = parenclose; - - } - else { + } else { return None; } if let Some(Token::Colon(_)) = parser.peek_token() { parser.advance(); - } - else { + } else { return None; } @@ -58,27 +51,22 @@ impl Macro { if parser.lexer.compare_range(macro_name, b"macro") { m.macro_pos = Token::Ident(macro_name.clone()); parser.advance(); - } - else { + } else { return None; } - } - else { + } else { return None; } if let Some(Token::Equal(_)) = parser.peek_token() { parser.advance(); - } - else { + } else { return None; } - if let Some(Token::BraceOpen(_)) = parser.peek_token() { parser.advance(); - } - else { + } else { return None; } @@ -95,17 +83,17 @@ impl Macro { None } - pub fn read_parenthesis(parser: &mut Parser) -> Option<(CursorPosition, Vec, CursorPosition)> { - - let mut start = CursorPosition::default(); - let mut end = CursorPosition::default(); + pub fn read_parenthesis( + parser: &mut Parser, + ) -> Option<(CursorPosition, Vec, CursorPosition)> { + let mut start = CursorPosition::default(); + let mut end = CursorPosition::default(); let mut tokens = Vec::new(); if let Some(Token::ParentOpen(parenopen)) = parser.peek_token() { start = *parenopen; parser.advance(); - } - else { + } else { return None; } @@ -128,154 +116,18 @@ impl Macro { // and we reached EOF // None - } - pub fn get_completion(&self, pos: &CursorPosition, items: &mut Vec) { + pub(crate) fn accept( + &self, + visitor: &mut V, + parser: &crate::parser::Parser, + ) { + visitor.visit_macro(self, parser); for e in self.body.iter() { - e.get_completion(pos, items); - } - } - - pub fn to_semantic_token(&self, semantic_tokens: &mut Vec, pre_line: &mut u32, pre_start: &mut u32, parser: &Parser) { - - semantic_tokens.push(get_range_token(&self.name, 4, pre_line, pre_start, parser)); - semantic_tokens.push(get_range_token(&self.macro_pos, 8, pre_line, pre_start, parser)); - - for e in self.body.iter() { - e.to_semantic_token(semantic_tokens, pre_line, pre_start, parser); - } - - // this doesn't work - /* - let mut arg_tokens = Vec::new(); - let start_inner = self.macro_pos.get_range().1; - if let Ok(inner_text) = String::from_utf8(parser.get_element_bytes(&(start_inner, self.end)) - .to_ascii_lowercase()) { - - let mut pline = *pre_line; - let mut pstart = *pre_start; - - for arg in self.args.iter() - .filter_map(|arg| String::from_utf8(parser.get_element_bytes(arg).to_ascii_lowercase()).ok()) { - log::debug!("look for arg {}", arg); - arg_tokens.extend(inner_text.match_indices(&arg).map(|(idx, _)| { - let mut pos0 = start_inner; - let mut pos1 = start_inner; - parser.lexer.advance_cursor(&mut pos0, idx); - parser.lexer.advance_cursor(&mut pos1, idx + arg.len()); - get_range_token(&(pos0, pos1), 9, &mut pline, &mut pstart, parser) - })); - } - } - - - - let mut expr_tokens = Vec::new(); - - for e in self.body.iter() { - e.to_semantic_token(&mut expr_tokens, pre_line, pre_start, parser); - } - - let mut arg_iter = arg_tokens.iter_mut(); - let mut exp_iter = expr_tokens.iter_mut(); - - let mut next_arg_token = arg_iter.next(); - let mut next_exp_token = exp_iter.next(); - - loop { - match (next_arg_token, next_exp_token) { - (Some(arg), Some(exp)) => { - if arg.delta_line < exp.delta_line { - exp.delta_line -= arg.delta_line; - semantic_tokens.push(*arg); - next_arg_token = arg_iter.next(); - next_exp_token = Some(exp); - log::debug!("push arg, delta line"); - } - else if arg.delta_start < exp.delta_start { - exp.delta_start -= arg.delta_start; - if arg.length > exp.delta_start { arg.length = exp.delta_start } - semantic_tokens.push(*arg); - next_arg_token = arg_iter.next(); - next_exp_token = Some(exp); - log::debug!("push arg, delta start"); - } - else if arg.delta_line > exp.delta_line{ - arg.delta_line -= exp.delta_line; - semantic_tokens.push(*exp); - next_exp_token = exp_iter.next(); - next_arg_token = Some(arg); - log::debug!("push exp, deltaline"); - } - else { - arg.delta_start -= exp.delta_start; - if exp.length > arg.delta_start { exp.length = arg.delta_start } - semantic_tokens.push(*exp); - next_exp_token = exp_iter.next(); - next_arg_token = Some(arg); - log::debug!("push exp, deltastart"); - } - continue; - }, - (Some(arg), None) => { - semantic_tokens.push(*arg); - next_arg_token = arg_iter.next(); - next_exp_token = None; - log::debug!("push arg, exps exhausted"); - continue; - }, - (None, Some(exp)) => { - semantic_tokens.push(*exp); - next_exp_token = exp_iter.next(); - next_arg_token = None; - log::debug!("push exp, args exhausted"); - continue; - }, - (None, None) => break, - } - - } - */ - } - - pub(crate) fn get_problems(&self, problems: &mut Vec) { - for e in self.body.iter() { - e.get_problems(problems); - } - } - - pub(crate) fn get_highlights(&self, pos: &CursorPosition, parser: &Parser) -> Vec<(CursorPosition, CursorPosition)> { - - let mut arg_tokens: Vec<(CursorPosition, CursorPosition)> = Vec::new(); - let mut arg_tokens = Vec::new(); - let start_inner = self.macro_pos.get_range().1; - if let Ok(inner_text) = String::from_utf8(parser.get_element_bytes(&(start_inner, self.end)) - .to_ascii_lowercase()) { - - - for arg in self.args.iter() - .filter_map(|arg| String::from_utf8(parser.get_element_bytes(arg).to_ascii_lowercase()).ok()) { - log::debug!("look for arg {}", arg); - arg_tokens.extend(inner_text.match_indices(&arg).map(|(idx, _)| { - let mut pos0 = start_inner; - let mut pos1 = start_inner; - parser.lexer.advance_cursor(&mut pos0, idx); - parser.lexer.advance_cursor(&mut pos1, idx + arg.len()); - (pos0, pos1) - })); - } - } - - arg_tokens - - } - - pub(crate) fn accept(&self, visitor: &mut V) { - visitor.visit_macro(self); - for e in self.body.iter() { - e.accept(visitor); + e.accept(visitor, parser); } + visitor.visit_macro_end(self, parser); } } @@ -291,19 +143,19 @@ mod tests { #[test] fn match_macro() { - let parser = Parser::from_str("m1(a, b): macro = {\n twiss,sequence=lhcb1;\na=b;\n}"); - let m = &parser.get_elements()[0]; if let Expression::Macro(m) = m { assert_eq!(parser.get_element_str(&m.name), "m1"); - assert_eq!(parser.get_element_str(m), "m1(a, b): macro = {\n twiss,sequence=lhcb1;\na=b;\n}"); + assert_eq!( + parser.get_element_str(m), + "m1(a, b): macro = {\n twiss,sequence=lhcb1;\na=b;\n}" + ); //assert!(false, "m: {:?}\n\n element after:\n{:?}", m, parser.get_elements()[1]); - } - else { + } else { assert!(false, "should be macro"); } assert_eq!(parser.get_elements().len(), 1); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d3a43e0..c0fb077 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2,10 +2,10 @@ use std::{ collections::HashMap, error::Error, fmt::Display, - path::{Path, PathBuf}, + path::{self, PathBuf}, }; -use tower_lsp::lsp_types::{Position, SemanticToken, SemanticTokenType, Url}; +use tower_lsp::lsp_types::{Range, SemanticTokenType, Url}; use crate::{ error::{MadxLsError, UTF8_PARSER_MSG}, @@ -15,6 +15,7 @@ use crate::{ pub mod assignment; pub mod expression; pub mod label; +pub mod madcall; pub mod madenvironment; pub mod madexec; pub mod madgeneric; @@ -25,6 +26,7 @@ pub mod problem; pub use assignment::*; pub use expression::*; pub use label::*; +pub use madcall::*; pub use madenvironment::*; pub use madexec::*; pub use madgeneric::*; @@ -37,10 +39,9 @@ pub struct Parser { pub uri: Option, pub lexer: Lexer, elements: Vec, - pub labels: HashMap, usize>, pub position: usize, - pub includes: Vec, - pub problems: Vec, + subparsers: HashMap, + missing_files: Vec, } pub const LEGEND_TYPE: &[SemanticTokenType] = &[ @@ -66,8 +67,11 @@ impl Parser { } pub fn from_path>(path: P) -> std::io::Result { - let lexer = Lexer::open(path)?; - Ok(Self::from_lexer(None, lexer)) + let lexer = Lexer::open(path.as_ref())?; + Ok(Self::from_lexer( + Some(Url::from_file_path(path.as_ref().canonicalize().unwrap()).unwrap()), + lexer, + )) } pub fn from_lexer(uri: Option, lexer: Lexer) -> Self { @@ -77,13 +81,11 @@ impl Parser { uri, lexer, elements: Vec::new(), - labels: HashMap::new(), - includes: Vec::new(), position: 0, - problems: Vec::new(), + subparsers: HashMap::new(), + missing_files: Vec::new(), }; parser.parse_elements(); - parser.scan_includes(); parser } @@ -108,79 +110,23 @@ impl Parser { //log::debug!("tokens: {:#?}", self.tokens); self.elements.clear(); - self.labels.clear(); self.parse_elements(); } - pub fn scan_includes(&mut self) { - log::info!("scanning includes"); - - let call_cmds = self.elements.iter().filter_map(|e| match e { - Expression::MadGeneric(g) => { - if g.match_name == b"call" { - Some(g) - } else { - None - } - } - _ => None, - }); - - log::debug!("call commands: {}", call_cmds.clone().count()); - - self.includes = call_cmds - .filter_map(|g| g.args.first()?.value.as_ref()) - .filter_map(|arg| { - get_path_relative_to_parent( - self.uri.as_ref(), - self.get_element_bytes(&**arg)[1..].to_vec(), - ) - }) - .filter_map(|filename| { - if let Some(fname) = filename.extension() { - log::debug!("filename include: {}", filename.display()); - if fname == "mad" || fname == "madx" { - if filename.exists() { - return Some(filename); - } - } - } - None - }) - .filter_map(|filename| Url::from_file_path(filename).ok()) - .collect::>(); - } - fn parse_elements(&mut self) { while let Some(expr) = Assignment::parse(self) { - match &expr { - Expression::Label(label) => { - self.labels.insert( - self.get_element_bytes(&label.name) - .to_ascii_lowercase() - .to_vec(), - self.elements.len(), - ); - } - Expression::Assignment(assignment) => { - self.labels.insert( - self.get_element_bytes(&*assignment.lhs) - .to_ascii_lowercase() - .to_vec(), - self.elements.len(), - ); - } - Expression::Macro(m) => { - self.labels.insert( - self.get_element_bytes(&m.name) - .to_ascii_lowercase() - .to_vec(), - self.elements.len(), - ); + if let Expression::Call(call) = &expr { + if let Some(uri) = call.get_filename(self) { + let url = self.get_subparser_uri(&uri).unwrap(); + if let Ok(mut subparser) = Parser::open(url.clone()) { + subparser.parse_elements(); + self.subparsers.insert(url, subparser); + } else { + self.missing_files + .push(self.lexer.cursor_range_to_text_range(&call.get_range())); + } } - _ => {} } - //expr.get_problems(&mut self.problems); self.elements.push(expr); } } @@ -193,6 +139,25 @@ impl Parser { &self.elements } + fn get_subparser_uri(&self, uri: &str) -> Option { + if let Some(mut self_uri) = self.uri.clone() { + let path_to_file = PathBuf::from(self_uri.path()); + self_uri.set_path(path_to_file.parent().unwrap().join(uri).to_str().unwrap()); + return Some(self_uri); + } + None + } + + pub fn get_subparser(&self, uri: &str) -> Option<&Parser> { + // check relative to self.uri + if let Some(mut self_uri) = self.get_subparser_uri(uri) { + if let Some(subparser) = self.subparsers.get(&self_uri) { + return Some(subparser); + } + } + None + } + pub fn peek_token(&self) -> Option<&Token> { self.lexer.get_tokens().get(self.position) } @@ -239,6 +204,10 @@ impl Parser { } None } + + pub fn get_missing_files(&self) -> &Vec { + &self.missing_files + } } impl Display for Parser { @@ -297,6 +266,7 @@ impl Display for Parser { Expression::Exec(_) => writeln!(f, "exec (??)")?, Expression::If(_) => writeln!(f, "if(...) {{ }}")?, Expression::Noop(_) => writeln!(f, "NOOP")?, + Expression::Call(_) => writeln!(f, "call (??)")?, } } Ok(()) @@ -355,39 +325,6 @@ mod tests { } } - /// this test initialises a parser from the string - /// "! hello\noption, echo, -warn;" and returns a list of parsed expressions - /// those are then converted to semantic tokens using the `to_semantic_token` function - #[test] - fn semantic_tokens() { - let mut parser = Parser::from_str("! hello\ncall, file;"); - let mut semantic_tokens = Vec::new(); - - let mut pstart = 0; - let mut pline = 0; - - let p = &mut parser; - - for e in p.get_elements() { - e.to_semantic_token(&mut semantic_tokens, &mut pline, &mut pstart, p); - } - } - - #[test] - fn parse_unfinished() { - let mut parser = Parser::from_str("call, \n! comment"); - let mut semantic_tokens = Vec::new(); - - let mut pstart = 0; - let mut pline = 0; - - let p = &mut parser; - - for e in p.get_elements() { - e.to_semantic_token(&mut semantic_tokens, &mut pline, &mut pstart, p); - } - } - #[test] fn parse_empty() { let parser = Parser::from_str(""); diff --git a/src/parser/problem.rs b/src/parser/problem.rs index a1c60d8..226bde9 100644 --- a/src/parser/problem.rs +++ b/src/parser/problem.rs @@ -1,15 +1,9 @@ use std::fmt::Display; -use tower_lsp::lsp_types::Range; +use tower_lsp::lsp_types; use crate::lexer::CursorPosition; -#[derive(Debug, Clone)] -pub struct MaybeProblem { - pub problem: Option, - pub range: Range -} - #[derive(Debug, Clone)] pub enum Problem { MissingCallee(Vec, (CursorPosition, CursorPosition)), @@ -19,10 +13,66 @@ pub enum Problem { Hint(String, CursorPosition, CursorPosition), } +impl Problem { + pub fn to_diagnostic(&self, parser: &crate::parser::Parser) -> lsp_types::Diagnostic { + use lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range}; + + let severity = match self { + Problem::MissingCallee(_, _) => DiagnosticSeverity::ERROR, + Problem::InvalidParam(_) => DiagnosticSeverity::ERROR, + Problem::Error(_, _, _) => DiagnosticSeverity::ERROR, + Problem::Warning(_, _, _) => DiagnosticSeverity::WARNING, + Problem::Hint(_, _, _) => DiagnosticSeverity::HINT, + }; + + let (start, end) = match self { + Problem::MissingCallee(_, range) => *range, + Problem::InvalidParam(range) => *range, + Problem::Error(_, start, end) => (*start, *end), + Problem::Warning(_, start, end) => (*start, *end), + Problem::Hint(_, start, end) => (*start, *end), + }; + + let message = match self { + Problem::MissingCallee(_, _) => "Missing Macro, check includes".to_string(), + Problem::InvalidParam(_) => "Invalid Mad Parameter".to_string(), + Problem::Error(msg, _, _) => msg.clone(), + Problem::Warning(msg, _, _) => msg.clone(), + Problem::Hint(msg, _, _) => msg.clone(), + }; + + let (start, end) = ( + parser.lexer.cursor_pos_to_text_pos(start), + parser.lexer.cursor_pos_to_text_pos(end), + ); + + Diagnostic { + range: Range { + start: Position { + line: start.line as u32, + character: start.character as u32, + }, + end: Position { + line: end.line as u32, + character: end.character as u32, + }, + }, + severity: Some(severity), + code: None, + code_description: None, + source: Some("madxls".to_string()), + message, + related_information: None, + tags: None, + data: None, + } + } +} + impl Display for Problem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Problem::MissingCallee(_,_) => write!(f, "Missing Macro, check includes"), + Problem::MissingCallee(_, _) => write!(f, "Missing Macro, check includes"), Problem::InvalidParam(_) => write!(f, "Invalid Mad Parameter"), Problem::Error(_, _, _) => todo!(), Problem::Warning(_, _, _) => todo!(), @@ -30,4 +80,3 @@ impl Display for Problem { } } } - diff --git a/src/rules/collect_labels.rs b/src/rules/collect_labels.rs new file mode 100644 index 0000000..94803fc --- /dev/null +++ b/src/rules/collect_labels.rs @@ -0,0 +1,60 @@ +use std::collections::HashSet; + +use crate::{ + lexer::CursorPosition, + parser::{Expression, Label, Macro, Parser}, + visitor::Visitor, +}; + +pub struct CollectLabels { + labels: HashSet>, +} + +impl CollectLabels { + pub fn new(parser: &Parser) -> Self { + let mut self_ = Self { + labels: HashSet::new(), + }; + self_.visit_parser(parser); + self_ + } + + pub fn get_labels(&self) -> &HashSet> { + &self.labels + } +} + +impl Visitor for CollectLabels { + fn visit_macro(&mut self, expression: &Macro, parser: &Parser) { + self.labels + .insert(parser.get_element_bytes(&expression.name).to_vec()); + } + fn visit_macro_end(&mut self, _: &Macro, _: &Parser) {} + + fn visit_label(&mut self, label: &Label, parser: &Parser) { + let label_str = parser.get_element_bytes(&label.name); + self.labels.insert(label_str.to_vec()); + } + + fn visit_if(&mut self, _: &crate::parser::If, _: &Parser) {} + + fn visit_exec(&mut self, _: &crate::parser::MadExec, _: &Parser) {} + + fn visit_generic(&mut self, _: &crate::parser::MadGeneric, _: &Parser) {} + + fn visit_call(&mut self, _: &crate::parser::MadCall, _: &Parser) {} + + fn visit_if_end(&mut self, _: &crate::parser::If, _: &Parser) {} + + fn visit_assignment_lhs(&mut self, _: &Expression, _: &Parser) {} + + fn visit_comment(&mut self, _: &(CursorPosition, CursorPosition), _: &Parser) {} + + fn visit_string(&mut self, _: &(CursorPosition, CursorPosition), _: &Parser) {} + + fn visit_subparser(&mut self, path: &str, parser: &crate::parser::Parser) { + if let Some(subparser) = parser.get_subparser(path) { + self.visit_parser(subparser); + } + } +} diff --git a/src/rules/macro_args.rs b/src/rules/macro_args.rs new file mode 100644 index 0000000..5c4b8b4 --- /dev/null +++ b/src/rules/macro_args.rs @@ -0,0 +1,102 @@ +use std::fmt::Display; + +use crate::{lexer::CursorPosition, parser::Parser, visitor::Visitor}; + +pub enum MacroArgProblem { + TooShort(tower_lsp::lsp_types::Range), + Unused(tower_lsp::lsp_types::Range), +} + +pub struct MacroArgs { + problems: Vec, +} + +impl MacroArgs { + pub fn new(parser: &Parser) -> Self { + let mut new_visitor = Self { + problems: Vec::new(), + }; + + new_visitor.visit_parser(parser); + new_visitor + } + + pub fn get_problems(&self) -> &Vec { + &self.problems + } +} + +impl Visitor for MacroArgs { + fn visit_macro(&mut self, macro_exp: &crate::parser::Macro, parser: &Parser) { + let mut macro_body = String::new(); + for expr in macro_exp.body.iter().filter(|e| match e { + crate::parser::Expression::Comment(_) => false, + _ => true, + }) { + macro_body.push_str(&parser.get_element_str(expr)); + macro_body.push(' '); // Add space to separate expressions + } + + for arg in macro_exp.args.iter() { + let range = parser.lexer.cursor_range_to_text_range(arg); + let arg_str = parser.get_element_str(arg); + + if range.end.character - range.start.character < 4 { + self.problems.push(MacroArgProblem::TooShort(range)); + } + + // Check if argument appears verbatim in the macro body + if !macro_body.contains(&arg_str) { + self.problems.push(MacroArgProblem::Unused(range)); + } + } + } + fn visit_exec(&mut self, _: &crate::parser::MadExec, _parser: &Parser) {} + + fn visit_label(&mut self, _: &crate::parser::Label, _parser: &Parser) {} + + fn visit_if(&mut self, _: &crate::parser::If, _parser: &Parser) {} + + fn visit_generic(&mut self, _: &crate::parser::MadGeneric, _parser: &Parser) {} + + fn visit_call(&mut self, _: &crate::parser::MadCall, _parser: &Parser) {} + + fn visit_macro_end(&mut self, _: &crate::parser::Macro, _parser: &Parser) {} + + fn visit_if_end(&mut self, _: &crate::parser::If, _parser: &Parser) {} + + fn visit_parser(&mut self, parser: &Parser) { + for e in parser.get_elements() { + e.accept(self, parser); + } + } + + fn visit_assignment_lhs(&mut self, _: &crate::parser::Expression, _parser: &Parser) {} + + fn visit_comment(&mut self, _: &(CursorPosition, CursorPosition), _parser: &Parser) {} + + fn visit_string(&mut self, _: &(CursorPosition, CursorPosition), _parser: &Parser) {} + + fn visit_subparser(&mut self, _: &str, _: &crate::parser::Parser) {} +} + +impl Display for MacroArgProblem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MacroArgProblem::TooShort(range) => { + write!( + f, + "MacroArgTooShort | ({}, {}) -- ({}, {})", + range.start.line, range.start.character, range.end.line, range.end.character, + ) + } + MacroArgProblem::Unused(range) => { + write!( + f, + "MacroArgUnused | ({}, {}) -- ({}, {})", + range.start.line, range.start.character, range.end.line, range.end.character, + ) + } + } + } +} diff --git a/src/rules/macro_variables.rs b/src/rules/macro_variables.rs new file mode 100644 index 0000000..b2f1de8 --- /dev/null +++ b/src/rules/macro_variables.rs @@ -0,0 +1,100 @@ +use std::{collections::HashMap, fmt::Display}; + +use tower_lsp::lsp_types::Range; + +use crate::{ + lexer::{print_range, CursorPosition}, + parser::{self, Expression, Parser}, + visitor::Visitor, +}; + +pub enum MacroVarProblem { + ExistWhileDef(Range, Range), + ExistsWhileCall(Range, Range), +} + +pub struct MacroVars { + problems: Vec, + labels: HashMap, Range>, +} + +impl MacroVars { + pub fn new(parser: &Parser) -> Self { + let mut new_visitor = Self { + problems: Vec::new(), + labels: HashMap::new(), + }; + + new_visitor.visit_parser(parser); + new_visitor + } + + pub fn get_problems(&self) -> &Vec { + &self.problems + } +} + +impl Visitor for MacroVars { + fn visit_macro(&mut self, macro_exp: &parser::Macro, parser: &Parser) { + for lhs in macro_exp.body.iter().filter_map(|e| match e { + Expression::Assignment(ass) => Some(ass.lhs.as_ref()), + _ => None, + }) { + if let Some(location) = self.labels.get(parser.get_element_bytes(lhs)) { + self.problems.push(MacroVarProblem::ExistWhileDef( + parser.lexer.cursor_range_to_text_range(lhs), + *location, + )); + } + } + } + fn visit_exec(&mut self, _: &parser::MadExec, _parser: &Parser) {} + + fn visit_label(&mut self, _: &parser::Label, _parser: &Parser) {} + + fn visit_if(&mut self, _: &parser::If, _parser: &Parser) {} + + fn visit_generic(&mut self, _: &parser::MadGeneric, _parser: &Parser) {} + + fn visit_call(&mut self, _: &parser::MadCall, _parser: &Parser) {} + + fn visit_macro_end(&mut self, _: &parser::Macro, _parser: &Parser) {} + + fn visit_if_end(&mut self, _: &parser::If, _parser: &Parser) {} + + fn visit_assignment_lhs(&mut self, lhs: &Expression, parser: &Parser) { + self.labels.insert( + parser.get_element_bytes(lhs).to_vec(), + parser.lexer.cursor_range_to_text_range(lhs), + ); + } + + fn visit_comment(&mut self, _: &(CursorPosition, CursorPosition), _parser: &Parser) {} + + fn visit_string(&mut self, _: &(CursorPosition, CursorPosition), _parser: &Parser) {} + + fn visit_subparser(&mut self, _: &str, _: &crate::parser::Parser) {} +} + +impl Display for MacroVarProblem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MacroVarProblem::ExistWhileDef(range, orig) => { + write!( + f, + "MacroVarExistDef | {} | {}", + print_range(range), + print_range(orig), + ) + } + MacroVarProblem::ExistsWhileCall(range, orig) => { + write!( + f, + "MacroVarExistCall | {} | {}", + print_range(range), + print_range(orig), + ) + } + } + } +} diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 0e26ef2..5a48340 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -1 +1,4 @@ +pub mod collect_labels; +pub mod macro_args; pub mod undefined_exec_call; +pub mod macro_variables; diff --git a/src/rules/undefined_exec_call.rs b/src/rules/undefined_exec_call.rs index 4c9c028..deb0967 100644 --- a/src/rules/undefined_exec_call.rs +++ b/src/rules/undefined_exec_call.rs @@ -1,27 +1,25 @@ -use std::env; +use std::collections::HashSet; -use crate::{ - lexer::{CursorPosition, HasRange}, - visitor::Visitor, -}; +use crate::{lexer::CursorPosition, parser::Parser, visitor::Visitor}; pub struct UndefinedExecCall<'a> { - parser: &'a crate::parser::Parser, - labels: Vec>, + labels: &'a HashSet>, problems: Vec<(CursorPosition, CursorPosition)>, } impl<'a> UndefinedExecCall<'a> { - pub fn new(parser: &'a crate::parser::Parser) -> Self { - Self { - parser, - labels: Vec::new(), + pub fn new(parser: &'a Parser, labels: &'a HashSet>) -> Self { + let mut new_visitor = Self { + labels, problems: Vec::new(), - } + }; + + new_visitor.visit_parser(parser); + new_visitor } pub fn check(&mut self, callee: &[u8], start: CursorPosition, end: CursorPosition) { - if !self.labels.iter().any(|l| l == callee) { + if !self.labels.contains(callee) { self.problems.push((start, end)); } } @@ -32,29 +30,29 @@ impl<'a> UndefinedExecCall<'a> { } impl<'a> Visitor for UndefinedExecCall<'a> { - fn visit_macro(&mut self, expression: &crate::parser::Macro) { - self.labels.push(self.parser.get_element_bytes(&expression.name).to_vec()); - } - fn visit_exec(&mut self, exec_exp: &crate::parser::MadExec) { + fn visit_macro(&mut self, _: &crate::parser::Macro, _parser: &Parser) {} + fn visit_exec(&mut self, exec_exp: &crate::parser::MadExec, parser: &Parser) { let callee = exec_exp.get_callee(); - let callee_str = self.parser.get_element_bytes(&callee); - println!("Checking callee: {}", String::from_utf8_lossy(callee_str)); - println!("against: {:?}", self.labels); - self.check(callee_str, exec_exp.get_range().0, exec_exp.get_range().1); - } - fn visit_label(&mut self, label: &crate::parser::Label) { - println!("Checking label: {}", self.parser.get_element_str(&label.name)); - let label_str = self.parser.get_element_bytes(&label.name.get_range()); - if !self.labels.iter().any(|l| l == label_str) { - self.labels.push(label_str.to_vec()); - } + let callee_str = parser.get_element_bytes(&callee); + self.check(callee_str, callee.0, callee.1); } + fn visit_label(&mut self, _: &crate::parser::Label, _parser: &Parser) {} - fn visit_if(&mut self, if_exp: &crate::parser::If) { - - } + fn visit_if(&mut self, _: &crate::parser::If, _parser: &Parser) {} - fn visit_generic(&mut self, generic: &crate::parser::MadGeneric) { - - } + fn visit_generic(&mut self, _: &crate::parser::MadGeneric, _parser: &Parser) {} + + fn visit_call(&mut self, _: &crate::parser::MadCall, _parser: &Parser) {} + + fn visit_macro_end(&mut self, _: &crate::parser::Macro, _parser: &Parser) {} + + fn visit_if_end(&mut self, _: &crate::parser::If, _parser: &Parser) {} + + fn visit_assignment_lhs(&mut self, _: &crate::parser::Expression, _parser: &Parser) {} + + fn visit_comment(&mut self, _: &(CursorPosition, CursorPosition), _parser: &Parser) {} + + fn visit_string(&mut self, _: &(CursorPosition, CursorPosition), _parser: &Parser) {} + + fn visit_subparser(&mut self, _: &str, _: &crate::parser::Parser) {} } diff --git a/src/visitor.rs b/src/visitor.rs index 0fb0a68..e751097 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -1,57 +1,157 @@ use std::fmt::Write; -use crate::{lexer::Token, parser::{Label, Macro, MadExec}}; - -pub trait Visitor { - fn visit_macro(&mut self, macro_exp: &Macro); - fn visit_exec(&mut self, exec_exp: &MadExec); - fn visit_label(&mut self, label: &Label); - fn visit_if(&mut self, if_exp: &crate::parser::If); - fn visit_generic(&mut self, generic: &crate::parser::MadGeneric); +use crate::{ + lexer::{CursorPosition, HasRange, Token}, + parser::{Label, Macro, MadExec, Parser}, +}; + +pub trait Visitor: Sized { + fn visit_macro(&mut self, macro_exp: &Macro, parser: &Parser); + fn visit_macro_end(&mut self, macro_exp: &Macro, parser: &Parser); + fn visit_exec(&mut self, exec_exp: &MadExec, parser: &Parser); + fn visit_label(&mut self, label: &Label, parser: &Parser); + fn visit_if(&mut self, if_exp: &crate::parser::If, parser: &Parser); + fn visit_if_end(&mut self, if_exp: &crate::parser::If, parser: &Parser); + fn visit_generic(&mut self, generic: &crate::parser::MadGeneric, parser: &Parser); + fn visit_call(&mut self, call_exp: &crate::parser::MadCall, parser: &Parser); + fn visit_assignment_lhs(&mut self, lhs: &crate::parser::Expression, parser: &Parser); + fn visit_comment(&mut self, comment: &(CursorPosition, CursorPosition), parser: &Parser); + fn visit_string(&mut self, string: &(CursorPosition, CursorPosition), parser: &Parser); + fn visit_subparser(&mut self, path: &str, parser: &crate::parser::Parser); + + fn visit_parser(&mut self, parser: &Parser) { + for e in parser.get_elements() { + e.accept(self, parser); + } + } } -pub struct PrintVisitor<'a> { - parser: &'a crate::parser::Parser, +pub struct PrintVisitor { indent: usize, pub buffer: String, } -impl<'a> PrintVisitor<'a> { - pub fn new(parser: &'a crate::parser::Parser) -> Self { - Self { - parser, +impl PrintVisitor { + pub fn new(parser: &Parser) -> Self { + let mut print_visitor = Self { indent: 0, buffer: String::new(), - } + }; + print_visitor.visit_parser(parser); + print_visitor + } + + pub fn indent_str(&self) -> String { + " ".repeat(self.indent) } } -fn print_token_exp(t: &Token, visitor: &mut PrintVisitor) { - match t { - Token::SemiColon(_) => { - writeln!(visitor.buffer, ";").unwrap(); +impl Visitor for PrintVisitor { + fn visit_macro(&mut self, macro_exp: &Macro, parser: &Parser) { + write!( + self.buffer, + "{}{}", + self.indent_str(), + parser.get_element_str(¯o_exp.name) + ) + .unwrap(); + write!(self.buffer, "(").unwrap(); + for (i, arg) in macro_exp.args.iter().enumerate() { + if i > 0 { + write!(self.buffer, ", ").unwrap(); + } + write!(self.buffer, "{}", parser.get_element_str(arg)).unwrap(); } - _ => {} + write!(self.buffer, "): MACRO = {{\n").unwrap(); + self.indent += 4; } -} -impl<'a> Visitor for PrintVisitor<'a> { - fn visit_macro(&mut self, macro_exp: &Macro) { - writeln!(self.buffer, "macro {} = {{", self.parser.get_element_str(¯o_exp.name)).unwrap(); + fn visit_macro_end(&mut self, _macro_exp: &Macro, _parser: &Parser) { + self.indent -= 4; + writeln!(self.buffer, "{}}}", self.indent_str()).unwrap(); } - fn visit_exec(&mut self, exec_exp: &MadExec) { - writeln!(self.buffer, "exec {}", self.parser.get_element_str(&exec_exp.get_callee())).unwrap(); + fn visit_exec(&mut self, exec_exp: &MadExec, parser: &Parser) { + writeln!( + self.buffer, + "{}EXEC, {}", + self.indent_str(), + parser.get_element_str(&(exec_exp.get_callee().0, exec_exp.get_range().1)) + ) + .unwrap(); } - fn visit_label(&mut self, label: &Label) { - writeln!(self.buffer, "label {}", self.parser.get_element_str(label)).unwrap(); + fn visit_label(&mut self, label: &Label, parser: &Parser) { + writeln!( + self.buffer, + "{}label {}", + self.indent_str(), + parser.get_element_str(label) + ) + .unwrap(); } - fn visit_if(&mut self, if_exp: &crate::parser::If) { - writeln!(self.buffer, "if").unwrap(); + fn visit_if(&mut self, if_exp: &crate::parser::If, parser: &Parser) { + // Print the IF header with condition + write!(self.buffer, "{}IF (", self.indent_str()).unwrap(); + for (i, cond) in if_exp.condition.iter().enumerate() { + if i > 0 { + write!(self.buffer, " ").unwrap(); + } + write!(self.buffer, "{}", parser.get_element_str(cond)).unwrap(); + } + writeln!(self.buffer, ") {{").unwrap(); + + // Increase indent for body + self.indent += 4; } - fn visit_generic(&mut self, generic: &crate::parser::MadGeneric) { - print_token_exp(&generic.name, self); + + fn visit_generic(&mut self, generic: &crate::parser::MadGeneric, _parser: &Parser) { + let indent = self.indent_str(); + match &generic.name { + Token::SemiColon(_) => { + writeln!(self.buffer, "{};", indent).unwrap(); + } + _ => {} + } } + + fn visit_call(&mut self, call_exp: &crate::parser::MadCall, parser: &Parser) { + writeln!( + self.buffer, + "{}CALL, {}", + self.indent_str(), + parser.get_element_str(&(call_exp.file_kw.get_range().0, call_exp.range.1)) + ) + .unwrap(); + } + + fn visit_if_end(&mut self, _: &crate::parser::If, _parser: &Parser) { + self.indent -= 4; + writeln!(self.buffer, "{}}}", self.indent_str()).unwrap(); + } + + fn visit_assignment_lhs(&mut self, lhs: &crate::parser::Expression, parser: &Parser) { + let _ = writeln!(self.buffer, "{} = ", parser.get_element_str(lhs)); + } + + fn visit_comment(&mut self, comment: &(CursorPosition, CursorPosition), parser: &Parser) { + let _ = writeln!( + self.buffer, + "{}// {}", + self.indent_str(), + parser.get_element_str(comment) + ); + } + + fn visit_string(&mut self, string: &(CursorPosition, CursorPosition), parser: &Parser) { + let _ = writeln!( + self.buffer, + "{}'{}'", + self.indent_str(), + parser.get_element_str(string) + ); + } + + fn visit_subparser(&mut self, _: &str, _: &crate::parser::Parser) {} } diff --git a/tests/input1.madx b/tests/input1.madx new file mode 100644 index 0000000..5ca2540 --- /dev/null +++ b/tests/input1.madx @@ -0,0 +1,6 @@ +multiply(arg_a, arg_b): macro = { + inner = arg_a * arg_b; + value, inner; +} + + diff --git a/tests/macro_args.madx b/tests/macro_args.madx new file mode 100644 index 0000000..9f23218 --- /dev/null +++ b/tests/macro_args.madx @@ -0,0 +1,14 @@ +vars_too_short(y, b): macro = { + value, y + b; +} + +bad_replacement(a, arg_b): macro = { + value, a + arg_b; +} + +unused(arg_a, arg_b): macro = { + value, arg_b; +} + +exec, vars_too_short(1, 2); + diff --git a/tests/with_errors.madx b/tests/with_errors.madx index e9202c4..0fa9339 100644 --- a/tests/with_errors.madx +++ b/tests/with_errors.madx @@ -1,3 +1,7 @@ +call, file=input1.madx; + +exec, multiply(2,3); + condition = 1; if(condition == 1) { @@ -16,7 +20,7 @@ fn_one(p1): macro = { } exec, fn_one(1); -exec, fn_one(-1); +exec, fn_two(-1); track_file(filename): macro = { ! Tracking routine, will output file named "trackone" @@ -29,3 +33,4 @@ track_file(filename): macro = { } exec, track_file("hello"); + From 3c1f1ea4db04ad5d2e983c8c089a92ee990f8c01 Mon Sep 17 00:00:00 2001 From: Andreas Wegscheider Date: Fri, 19 Sep 2025 15:48:36 +0200 Subject: [PATCH 2/4] re-enable code highlighting in server --- README.md | 12 +++---- bin/scanner.rs | 67 ++++++++++++++++++++---------------- bin/server.rs | 71 ++++++++++++++------------------------ src/document.rs | 57 +++++++++++------------------- src/highlighter.rs | 78 ++++++++++++++++++++++++++++++++++++++---- src/lexer/mod.rs | 30 ++++++++++++++++ src/lib.rs | 1 - src/parser/label.rs | 2 +- src/parser/mod.rs | 14 ++++---- src/semantic_tokens.rs | 34 ------------------ tests/with_errors.madx | 1 + 11 files changed, 197 insertions(+), 170 deletions(-) delete mode 100644 src/semantic_tokens.rs diff --git a/README.md b/README.md index 55ceaf0..401aa98 100644 --- a/README.md +++ b/README.md @@ -52,13 +52,10 @@ The scanner is the most stable and feature-complete component: ```bash # Analyze a single file -madx_scanner analyze path/to/your/script.madx +madx_scanner --input-file path/to/your/script.madx -# Analyze a directory -madx_scanner analyze path/to/project/ - -# Generate detailed metrics -madx_scanner metrics path/to/your/script.madx +# Analyze a single file, print highlights and code metrics +madx_scanner --input-file path/to/your/script.madx --highlight --metrics ``` ### LSP Server (Under Development) @@ -87,7 +84,7 @@ Configure using your preferred LSP client (e.g., `lsp-mode` or `eglot`). ## SonarQube Integration -The scanner includes a SonarQube plugin that enables continuous code quality monitoring: +The scanner integrates via a dedicated [SonarQube plugin](https://github.com/awegsche/sonar-madx) that enables continuous code quality monitoring: - **On-Premise SonarQube**: Full support for self-hosted SonarQube instances - **Cloud Support**: Currently limited due to plugin deployment constraints (coming soon) @@ -98,7 +95,6 @@ We welcome contributions! The project is actively developed, and we're particula - LSP server stability improvements - Additional code quality rules - Enhanced editor integrations -- SonarQube cloud deployment solutions ## Roadmap diff --git a/bin/scanner.rs b/bin/scanner.rs index 38d7438..0893a73 100644 --- a/bin/scanner.rs +++ b/bin/scanner.rs @@ -1,4 +1,4 @@ -use clap::Parser; +use clap::{Parser, Subcommand}; use madxls::highlighter::Highlighter; use madxls::lexer::print_range; use madxls::measures::code_blocks::CodeBlocks; @@ -12,50 +12,57 @@ use madxls::rules::{ #[derive(Parser, Debug)] #[command(version, about)] struct Args { - #[arg(long)] pub input_file: Option, - #[arg(long, default_value = "false")] + #[arg(long)] + pub metrics: bool, + + #[arg(long)] pub highlight: bool, } fn main() { let args = Args::parse(); - if let Some(file) = args.input_file { - let parser = parser::Parser::from_path(file).unwrap(); + let Some(file) = args.input_file else { + println!("No input file provided"); + return; + }; - for mf in parser.get_missing_files().iter() { - println!("MissingFile | {}", print_range(mf)); - } + let parser = parser::Parser::from_path(file).unwrap(); - let collect_labels = CollectLabels::new(&parser); - let missing_callee = UndefinedExecCall::new(&parser, collect_labels.get_labels()); - let macro_args = MacroArgs::new(&parser); - let macro_vars = MacroVars::new(&parser); + for mf in parser.get_missing_files().iter() { + println!("MissingFile | {}", print_range(mf)); + } - for p in missing_callee.get_problems().iter() { - let range = parser.lexer.cursor_range_to_text_range(p); + let collect_labels = CollectLabels::new(&parser); + let missing_callee = UndefinedExecCall::new(&parser, collect_labels.get_labels()); + let macro_args = MacroArgs::new(&parser); + let macro_vars = MacroVars::new(&parser); - println!( - "MissingCallee | ({}, {}) -- ({}, {})", - range.start.line, range.start.character, range.end.line, range.end.character - ); - } - for ma in macro_args.get_problems().iter() { - println!("{}", ma); - } - for mv in macro_vars.get_problems().iter() { - println!("{}", mv); - } + for p in missing_callee.get_problems().iter() { + let range = parser.lexer.cursor_range_to_text_range(p); - if args.highlight { - let highlighter = Highlighter::new(&parser); - for h in highlighter.highlights.iter() { - println!("{}", h); - } + println!( + "MissingCallee | ({}, {}) -- ({}, {})", + range.start.line, range.start.character, range.end.line, range.end.character + ); + } + for ma in macro_args.get_problems().iter() { + println!("{}", ma); + } + for mv in macro_vars.get_problems().iter() { + println!("{}", mv); + } + + if args.highlight { + let highlighter = Highlighter::new(&parser); + for h in highlighter.highlights.iter() { + println!("{}", h); } + } + if args.metrics { let loc = Loc::new(&parser); println!("loc: {}", loc.lines.len()); diff --git a/bin/server.rs b/bin/server.rs index effc6de..3bd75b4 100644 --- a/bin/server.rs +++ b/bin/server.rs @@ -3,41 +3,35 @@ use std::sync::Arc; use clap::Parser; use dashmap::DashMap; use log::LevelFilter; -use log4rs::append::file::FileAppender; -use log4rs::config::Appender; -use log4rs::config::Root; -use log4rs::encode::pattern::PatternEncoder; -use log4rs::Config; -use parser::Problem; -use parser::LEGEND_TYPE; +use log4rs::{ + append::file::FileAppender, + config::{Appender, Root}, + encode::pattern::PatternEncoder, + Config, +}; +use madxls::document; +use madxls::parser::{Problem, LEGEND_TYPE}; use tower_lsp::jsonrpc::Result; -use tower_lsp::lsp_types::*; -use tower_lsp::{Client, LanguageServer, LspService, Server}; - -use madxls::*; - -#[derive(Parser, Debug)] -#[command(version, about)] -struct Args { - #[arg(long)] - pub debug_file: Option, -} +use tower_lsp::{ + lsp_types::{ + CompletionItem, CompletionOptions, CompletionParams, CompletionResponse, + DidChangeTextDocumentParams, DidOpenTextDocumentParams, DocumentFilter, DocumentHighlight, + DocumentHighlightOptions, DocumentHighlightParams, Hover, HoverParams, + HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, + MessageType, OneOf, Position, SemanticTokensFullOptions, SemanticTokensLegend, + SemanticTokensOptions, SemanticTokensParams, SemanticTokensRegistrationOptions, + SemanticTokensResult, SemanticTokensServerCapabilities, ServerCapabilities, + StaticRegistrationOptions, TextDocumentRegistrationOptions, TextDocumentSyncCapability, + TextDocumentSyncKind, Url, WillSaveTextDocumentParams, WorkDoneProgressOptions, + }, + Client, LanguageServer, LspService, Server, +}; #[tokio::main] async fn main() { - //debug::debug_parser(); - // - let args = Args::parse(); - - if let Some(file) = args.debug_file { - debug::print_ast(file); - return; - } - - //return; let logfile = FileAppender::builder() .encoder(Box::new(PatternEncoder::new("{l} - {m}\n"))) - .build("/home/awegsche/logs/logfile.log") + .build("logfile.log") .unwrap(); let config = Config::builder() @@ -143,13 +137,7 @@ impl LanguageServer for Backend { if let Some(doc) = self .documents .get(¶ms.text_document_position_params.text_document.uri) - { - //let hi = doc.get_document_highlights(¶ms.text_document_position_params.position); - //if let Ok(Some(h)) = &hi { - // log::debug!("get some highlights: {}", h.len()); - //} - //return hi; - } + {} Ok(None) } @@ -158,7 +146,6 @@ impl LanguageServer for Backend { } async fn did_open(&self, params: DidOpenTextDocumentParams) { - log::info!("did open"); self.client .log_message(MessageType::INFO, "did open!") .await; @@ -230,7 +217,7 @@ impl LanguageServer for Backend { ) -> Result> { log::info!("semantic tokens full"); if let Some(document) = self.documents.get(¶ms.text_document.uri) { - //return document.get_semantic_tokens(); + return document.get_semantic_tokens(); } Ok(None) } @@ -290,16 +277,10 @@ fn get_completions( url: &Url, documents: &Arc>, ) { + let _ = items; if let Some(doc) = documents.get(url) {} } -fn diagnostics_from_problems( - problems: &[Problem], - parser: &crate::parser::Parser, -) -> Vec { - problems.iter().map(|p| p.to_diagnostic(parser)).collect() -} - async fn run_server() { let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); diff --git a/src/document.rs b/src/document.rs index b4a549c..b7c5098 100644 --- a/src/document.rs +++ b/src/document.rs @@ -1,13 +1,11 @@ use std::path::Path; +use tower_lsp::lsp_types::SemanticTokensResult; use tower_lsp::jsonrpc::Result; -use tower_lsp::lsp_types::{ - CompletionItem, CompletionItemKind, DocumentHighlight, MarkedString, Position, Range, - SemanticTokens, SemanticTokensResult, Url, -}; +use tower_lsp::lsp_types::{SemanticTokens, Url}; -use crate::error::UTF8_PARSER_MSG; -use crate::parser::{Expression, Parser, Problem, GENERIC_BUILTINS}; +use crate::highlighter::Highlighter; +use crate::parser::Parser; #[derive(Debug)] pub struct Document { @@ -32,39 +30,24 @@ impl Document { self.parser = Parser::from_bytes(text.to_vec(), uri); //self.parser.scan_includes(); } -} - -pub fn sanitize_string_for_md(s: String) -> String { - s.replace("*", "\\*").replace("_", "\\_") -} - -#[cfg(test)] -mod tests { - use super::*; + pub fn get_semantic_tokens(&self) -> Result> { + let highlighter = Highlighter::new(&self.parser); - #[test] - fn test_macros() { - let elements = vec![ - "// test file", ";", - "/* this is a multiline comment\n* explaining what the macro does\n* in a very detailed way */", - "do_twiss(filename): macro = {\n twiss, sequence=lhcb1;\n}", - ";", - ]; - let doc = Document::new(None, elements.join("\n").as_bytes()); - let expressions = doc.parser.get_elements(); + let mut pline = 1; + let mut pstart = 0; - assert_eq!(doc.parser.get_element_str(&expressions[0]), elements[0]); - assert_eq!(doc.parser.get_element_str(&expressions[2]), elements[2]); - if let Expression::Macro(m) = &expressions[3] { - assert_eq!(doc.parser.get_element_str(m), elements[3]); - } else { - assert!( - false, - "exprected macro, got: {:?}\nrange: {}", - expressions[3], - doc.parser.get_element_str(&expressions[3]) - ); - } + Ok(Some(SemanticTokensResult::Tokens(SemanticTokens { + result_id: None, + data: highlighter + .highlights + .iter() + .map(|h| h.into_semantic_token(&mut pline, &mut pstart, &self.parser)) + .collect(), + }))) } } + +pub fn sanitize_string_for_md(s: String) -> String { + s.replace("*", "\\*").replace("_", "\\_") +} diff --git a/src/highlighter.rs b/src/highlighter.rs index d3b8ed6..788a460 100644 --- a/src/highlighter.rs +++ b/src/highlighter.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use tower_lsp::lsp_types::Range; +use tower_lsp::lsp_types::{Range, SemanticToken}; use crate::{ lexer::{print_range, CursorPosition}, @@ -19,16 +19,14 @@ pub enum Highlight { String(Range), } -pub struct Highlighter<'a> { +pub struct Highlighter { pub highlights: Vec, - pub parser: &'a Parser, } -impl<'a> Highlighter<'a> { - pub fn new(parser: &'a Parser) -> Self { +impl Highlighter { + pub fn new(parser: &Parser) -> Self { let mut highlighter = Self { highlights: Vec::new(), - parser, }; highlighter.visit_parser(parser); @@ -36,7 +34,7 @@ impl<'a> Highlighter<'a> { } } -impl Visitor for Highlighter<'_> { +impl Visitor for Highlighter { fn visit_macro(&mut self, macro_exp: &crate::parser::Macro, parser: &Parser) { self.highlights.push(Highlight::Type( parser.lexer.cursor_range_to_text_range(¯o_exp.name), @@ -54,6 +52,11 @@ impl Visitor for Highlighter<'_> { self.highlights.push(Highlight::Keyword( parser.lexer.cursor_range_to_text_range(&exec_exp.name), )); + self.highlights.push(Highlight::Function( + parser + .lexer + .cursor_range_to_text_range(&exec_exp.get_callee()), + )); } fn visit_label(&mut self, label: &crate::parser::Label, parser: &Parser) { @@ -74,6 +77,12 @@ impl Visitor for Highlighter<'_> { self.highlights.push(Highlight::Function( parser.lexer.cursor_range_to_text_range(&generic.name), )); + + for kw in generic.args.iter() { + self.highlights.push(Highlight::Parameter( + parser.lexer.cursor_range_to_text_range(&kw.attribute), + )); + } } fn visit_call(&mut self, call_exp: &crate::parser::MadCall, parser: &Parser) { @@ -113,3 +122,58 @@ impl Display for Highlight { } } } + +pub fn get_range_token( + range: Range, + token_type: u32, + pline: &mut u32, + pstart: &mut u32, + parser: &Parser, +) -> SemanticToken { + let line = range.start.line; + let start = range.start.character; + let delta_line = line - *pline; + let delta_start = if delta_line == 0 { + start - *pstart + } else { + start + }; + + let length = parser.lexer.get_length_range(&range) as u32; + + let token = SemanticToken { + delta_line, + delta_start, + length, + token_type, + token_modifiers_bitset: 0, + }; + + log::debug!("token: {:#?}", token); + + *pline = line; + *pstart = start; + + token +} + +impl Highlight { + pub fn into_semantic_token( + &self, + pline: &mut u32, + pstart: &mut u32, + parser: &Parser, + ) -> SemanticToken { + let (kind, range) = match self { + Highlight::Keyword(range) => (0, range), + Highlight::Type(range) => (1, range), + Highlight::Class(range) => (2, range), + Highlight::Function(range) => (3, range), + Highlight::Parameter(range) => (4, range), + Highlight::Comment(range) => (5, range), + Highlight::Constant(range) => (6, range), + Highlight::String(range) => (7, range), + }; + get_range_token(*range, kind, pline, pstart, parser) + } +} diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 3119d73..a7e4ab5 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -102,6 +102,18 @@ impl Lexer { } } + pub fn get_length_range(&self, range: &tower_lsp::lsp_types::Range) -> usize { + let start = range.start; + let end = range.end; + + if start.line == end.line { + (end.character - start.character) as usize + } else { + self.lines[end.line as usize] - self.lines[start.line as usize] + end.character as usize + - start.character as usize + } + } + /// advancing the CursorPosition `cursor` by `by` characters, taking into account line breaks pub fn advance_cursor(&self, cursor: &mut CursorPosition, by: usize) { *cursor += by; @@ -380,6 +392,8 @@ impl Display for Lexer { #[cfg(test)] mod tests { + use tower_lsp::lsp_types::Range; + use super::*; pub fn check_string(buffer: &[u8], tokens: &[&str]) { @@ -555,4 +569,20 @@ mod tests { assert!(false, "Expected Equal"); } } + + #[test] + fn get_length_range() { + let lexer = Lexer::from_str("first;\nsecond"); + let range = Range { + start: Position::new(0, 0), + end: Position::new(0, 5), + }; + assert_eq!(lexer.get_length_range(&range), 5); + + let range = Range { + start: Position::new(0, 0), + end: Position::new(1, 5), + }; + assert_eq!(lexer.get_length_range(&range), 12); + } } diff --git a/src/lib.rs b/src/lib.rs index 895e8ae..81ea1f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ pub mod lexer; pub mod measures; pub mod parser; pub mod rules; -pub mod semantic_tokens; pub mod visitor; pub mod debug; diff --git a/src/parser/label.rs b/src/parser/label.rs index 6785108..02b8138 100644 --- a/src/parser/label.rs +++ b/src/parser/label.rs @@ -22,7 +22,7 @@ impl Label { // try parsing as MAdGeneric if let Some(mad_generic) = MadGeneric::parse(parser) { return Some(Self { - name: name, + name, command: mad_generic, }); } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c0fb077..002b6b3 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -45,14 +45,14 @@ pub struct Parser { } pub const LEGEND_TYPE: &[SemanticTokenType] = &[ - SemanticTokenType::TYPE, // 0 - SemanticTokenType::STRING, // 1 - SemanticTokenType::COMMENT, // 2 - SemanticTokenType::OPERATOR, // 3 - SemanticTokenType::FUNCTION, // 4 - SemanticTokenType::PARAMETER, // 5 + SemanticTokenType::KEYWORD, // 0 + SemanticTokenType::TYPE, // 1 + SemanticTokenType::CLASS, // 2 + SemanticTokenType::FUNCTION, // 3 + SemanticTokenType::PARAMETER, // 4 + SemanticTokenType::COMMENT, // 5 SemanticTokenType::MACRO, // 6 - SemanticTokenType::NAMESPACE, // 7 + SemanticTokenType::STRING, // 7 SemanticTokenType::KEYWORD, // 8 SemanticTokenType::KEYWORD, // 9 ]; diff --git a/src/semantic_tokens.rs b/src/semantic_tokens.rs deleted file mode 100644 index bee375e..0000000 --- a/src/semantic_tokens.rs +++ /dev/null @@ -1,34 +0,0 @@ -use tower_lsp::lsp_types::SemanticToken; - -use crate::{lexer::HasRange, parser::Parser}; - -pub fn get_range_token( token: &R, token_type: u32, pline: &mut u32, - pstart: &mut u32, parser: &Parser) -> SemanticToken { - - let range = token.get_range(); - let line = range.0.line() as u32; - let start = range.0.character(parser.lexer.lines()) as u32; - let delta_line = line - *pline; - let length = (range.1.absolute() - range.0.absolute()) as u32; - let delta_start = if delta_line == 0 { - start - *pstart - } - else { - start - }; - - - let token = SemanticToken { - delta_line, - delta_start, - length, - token_type, - token_modifiers_bitset: 0 - }; - - *pline = line; - *pstart = start; - - token -} - diff --git a/tests/with_errors.madx b/tests/with_errors.madx index 0fa9339..36923e8 100644 --- a/tests/with_errors.madx +++ b/tests/with_errors.madx @@ -21,6 +21,7 @@ fn_one(p1): macro = { exec, fn_one(1); exec, fn_two(-1); +exec, fn_one(-1); track_file(filename): macro = { ! Tracking routine, will output file named "trackone" From 55926695b918c34eb1c90fc740a8877477da1e5b Mon Sep 17 00:00:00 2001 From: awegsche Date: Fri, 19 Sep 2025 15:57:16 +0200 Subject: [PATCH 3/4] add quality gate and coverage badges --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 401aa98..34ff3bb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# MADX-LS +# MADX-LS +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=awegsche1_madxls&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=awegsche1_madxls) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=awegsche1_madxls&metric=coverage)](https://sonarcloud.io/summary/new_code?id=awegsche1_madxls) A comprehensive language toolchain for the [MADX](http://mad.web.cern.ch/mad/) scripting language, providing both IDE integration and static analysis capabilities. @@ -105,4 +107,4 @@ We welcome contributions! The project is actively developed, and we're particula - [ ] Hover documentation for built-in commands - [ ] Jump-to-definition functionality - [ ] SonarQube cloud plugin deployment -- [ ] VS Code extension development \ No newline at end of file +- [ ] VS Code extension development From 6543be8bd234f940461e18d921338b9073cd2b25 Mon Sep 17 00:00:00 2001 From: Andreas Wegscheider Date: Sun, 21 Sep 2025 22:10:19 +0200 Subject: [PATCH 4/4] refactor issues - clean up some code - use issue struct to report errors both to server and scanner --- bin/scanner.rs | 26 +----- bin/server.rs | 84 ++++++++---------- sonar-project.properties | 5 +- src/document.rs | 8 +- src/parser/madexec.rs | 13 --- src/parser/madgeneric.rs | 31 ------- src/parser/madmacro.rs | 5 +- src/parser/mod.rs | 33 +------ src/rules/issues.rs | 143 +++++++++++++++++++++++++++++++ src/rules/macro_args.rs | 1 + src/rules/macro_variables.rs | 1 + src/rules/mod.rs | 3 +- src/rules/undefined_exec_call.rs | 42 +++++++-- 13 files changed, 235 insertions(+), 160 deletions(-) create mode 100644 src/rules/issues.rs diff --git a/bin/scanner.rs b/bin/scanner.rs index 0893a73..cf6c893 100644 --- a/bin/scanner.rs +++ b/bin/scanner.rs @@ -4,6 +4,7 @@ use madxls::lexer::print_range; use madxls::measures::code_blocks::CodeBlocks; use madxls::measures::loc::Loc; use madxls::parser; +use madxls::rules::issues::Issues; use madxls::rules::{ collect_labels::CollectLabels, macro_args::MacroArgs, macro_variables::MacroVars, undefined_exec_call::UndefinedExecCall, @@ -31,29 +32,8 @@ fn main() { let parser = parser::Parser::from_path(file).unwrap(); - for mf in parser.get_missing_files().iter() { - println!("MissingFile | {}", print_range(mf)); - } - - let collect_labels = CollectLabels::new(&parser); - let missing_callee = UndefinedExecCall::new(&parser, collect_labels.get_labels()); - let macro_args = MacroArgs::new(&parser); - let macro_vars = MacroVars::new(&parser); - - for p in missing_callee.get_problems().iter() { - let range = parser.lexer.cursor_range_to_text_range(p); - - println!( - "MissingCallee | ({}, {}) -- ({}, {})", - range.start.line, range.start.character, range.end.line, range.end.character - ); - } - for ma in macro_args.get_problems().iter() { - println!("{}", ma); - } - for mv in macro_vars.get_problems().iter() { - println!("{}", mv); - } + let issues = Issues::from_parser(&parser); + issues.print_problems(&mut std::io::stdout()).unwrap(); if args.highlight { let highlighter = Highlighter::new(&parser); diff --git a/bin/server.rs b/bin/server.rs index 3bd75b4..525020c 100644 --- a/bin/server.rs +++ b/bin/server.rs @@ -115,34 +115,8 @@ impl LanguageServer for Backend { .await; } - async fn completion(&self, params: CompletionParams) -> Result> { - log::info!("completion"); - let uri = params.text_document_position.text_document.uri; - let mut items = Vec::new(); - - get_completions( - &mut items, - Some(params.text_document_position.position), - &uri, - &self.documents, - ); - Ok(Some(CompletionResponse::Array(items))) - } - - async fn document_highlight( - &self, - params: DocumentHighlightParams, - ) -> Result>> { - log::debug!("highlights triggered"); - if let Some(doc) = self - .documents - .get(¶ms.text_document_position_params.text_document.uri) - {} - Ok(None) - } - - async fn will_save(&self, params: WillSaveTextDocumentParams) { - self.resubmit_diagnostics(¶ms.text_document.uri).await; + async fn shutdown(&self) -> Result<()> { + Ok(()) } async fn did_open(&self, params: DidOpenTextDocumentParams) { @@ -154,18 +128,8 @@ impl LanguageServer for Backend { let document = document::Document::new(Some(uri.clone()), params.text_document.text.as_bytes()); - // check the includes - /* - let includes = document.parser.includes.clone(); - let docs = self.documents.clone(); - tokio::spawn(async move { - for incl in includes.into_iter() { - reload_includes(incl, &docs); - } - }); - */ - self.documents.insert(uri.clone(), document); + self.resubmit_diagnostics(uri).await; } } @@ -188,6 +152,22 @@ impl LanguageServer for Backend { self.resubmit_diagnostics(¶ms.text_document.uri).await; } + async fn will_save(&self, params: WillSaveTextDocumentParams) { + self.resubmit_diagnostics(¶ms.text_document.uri).await; + } + + async fn document_highlight( + &self, + params: DocumentHighlightParams, + ) -> Result>> { + log::debug!("highlights triggered"); + if let Some(doc) = self + .documents + .get(¶ms.text_document_position_params.text_document.uri) + {} + Ok(None) + } + async fn hover(&self, params: HoverParams) -> Result> { log::info!("hover"); if let Some(doc) = self @@ -222,8 +202,18 @@ impl LanguageServer for Backend { Ok(None) } - async fn shutdown(&self) -> Result<()> { - Ok(()) + async fn completion(&self, params: CompletionParams) -> Result> { + log::info!("completion"); + let uri = params.text_document_position.text_document.uri; + let mut items = Vec::new(); + + get_completions( + &mut items, + Some(params.text_document_position.position), + &uri, + &self.documents, + ); + Ok(Some(CompletionResponse::Array(items))) } } @@ -258,15 +248,11 @@ impl Backend { log::debug!("try resubmit"); if let Some(doc) = self.documents.get(uri) { - ////let problems = doc.get_diagnostics(); + let problems = doc.get_diagnostics(); - //self.client - // .publish_diagnostics( - // uri.clone(), - // diagnostics_from_problems(&problems, &doc.parser), - // None, - // ) - // .await; + self.client + .publish_diagnostics(uri.clone(), problems, None) + .await; } } } diff --git a/sonar-project.properties b/sonar-project.properties index 26d3f9d..50ae386 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,9 @@ -sonar.projectKey=awegsche1_madxls +sonar.projectKey=madxls sonar.organization=awegsche1 +sonar.rust.lcov.reportPaths=target/coverage/tests.lcov + +sonar.exclusions=**/*.madx # This is the name and version displayed in the SonarCloud UI. #sonar.projectName=madxls diff --git a/src/document.rs b/src/document.rs index b7c5098..633b6a0 100644 --- a/src/document.rs +++ b/src/document.rs @@ -1,11 +1,12 @@ use std::path::Path; -use tower_lsp::lsp_types::SemanticTokensResult; +use tower_lsp::lsp_types::{Diagnostic, SemanticTokensResult}; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::{SemanticTokens, Url}; use crate::highlighter::Highlighter; use crate::parser::Parser; +use crate::rules::issues::Issues; #[derive(Debug)] pub struct Document { @@ -46,6 +47,11 @@ impl Document { .collect(), }))) } + + pub fn get_diagnostics(&self) -> Vec { + let issues = Issues::from_parser(&self.parser); + issues.to_diagnostics() + } } pub fn sanitize_string_for_md(s: String) -> String { diff --git a/src/parser/madexec.rs b/src/parser/madexec.rs index 0415500..d94083f 100644 --- a/src/parser/madexec.rs +++ b/src/parser/madexec.rs @@ -44,19 +44,6 @@ impl MadExec { None } - pub(crate) fn get_label<'a>( - &'a self, - pos: &CursorPosition, - parser: &'a super::Parser, - ) -> Option<&'a [u8]> { - let range = self.callee.get_range(); - if &range.0 < pos && pos < &range.1 { - Some(parser.get_element_bytes(&range)) - } else { - None - } - } - pub fn get_callee(&self) -> (CursorPosition, CursorPosition) { self.callee.get_range() } diff --git a/src/parser/madgeneric.rs b/src/parser/madgeneric.rs index 0733e4d..1b0a872 100644 --- a/src/parser/madgeneric.rs +++ b/src/parser/madgeneric.rs @@ -535,34 +535,3 @@ pub fn insert_generic_builder_known_flags( }, ); } - -#[cfg(test)] -mod tests { - use crate::parser::{Expression, Parser}; - - //#[test] - //pub fn incomplete() { - // let parser = Parser::from_str("call, fi"); - - // let call = &parser.get_elements()[0]; - - // if let Expression::MadGeneric(g) = call { - // assert_eq!(parser.get_element_str(&g.name), "call"); - // } else { - // assert!(false, "this should be recognized as incomplete CALL"); - // } - //} - - //#[test] - //pub fn incomplete_inside() { - // let parser = Parser::from_str("call, fi\ntwiss, sequence=lhcb1;"); - - // let call = &parser.get_elements()[0]; - - // if let Expression::MadGeneric(g) = call { - // assert_eq!(parser.get_element_str(&g.name), "call"); - // } else { - // assert!(false, "this should be recognized as incomplete CALL"); - // } - //} -} diff --git a/src/parser/madmacro.rs b/src/parser/madmacro.rs index ad6e002..437059d 100644 --- a/src/parser/madmacro.rs +++ b/src/parser/madmacro.rs @@ -86,10 +86,9 @@ impl Macro { pub fn read_parenthesis( parser: &mut Parser, ) -> Option<(CursorPosition, Vec, CursorPosition)> { - let mut start = CursorPosition::default(); - let mut end = CursorPosition::default(); let mut tokens = Vec::new(); + let mut start = CursorPosition::default(); if let Some(Token::ParentOpen(parenopen)) = parser.peek_token() { start = *parenopen; parser.advance(); @@ -101,7 +100,7 @@ impl Macro { parser.advance(); match token { Token::ParentClose(parenclose) => { - end = parenclose; + let end = parenclose; return Some((start, tokens, end)); } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 002b6b3..6cf3186 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,9 +1,4 @@ -use std::{ - collections::HashMap, - error::Error, - fmt::Display, - path::{self, PathBuf}, -}; +use std::{collections::HashMap, error::Error, fmt::Display, path::PathBuf}; use tower_lsp::lsp_types::{Range, SemanticTokenType, Url}; @@ -150,7 +145,7 @@ impl Parser { pub fn get_subparser(&self, uri: &str) -> Option<&Parser> { // check relative to self.uri - if let Some(mut self_uri) = self.get_subparser_uri(uri) { + if let Some(self_uri) = self.get_subparser_uri(uri) { if let Some(subparser) = self.subparsers.get(&self_uri) { return Some(subparser); } @@ -273,30 +268,6 @@ impl Display for Parser { } } -/// we assume that madx scripts are runnable in their respective working directory, -/// so we search for includes there. -/// -/// If this fails, we return the path as-is (i.e. relative to current working dir) nevertheless, -/// because we are very permissive here. -/// This might, of course, lead to false positives which could be problematic for the workflow. -/// -/// # Params: -/// * `uri` - the Url of the parent document (.madx script) -/// * `bytes` - the bytes from `Parser::get_element_bytes()` from the `"call"` `MadGeneric` -fn get_path_relative_to_parent(uri: Option<&Url>, bytes: Vec) -> Option { - let call_path = String::from_utf8(bytes).ok()?; - if let Some(uri) = uri { - let root = uri.to_file_path().ok()?.parent()?.to_path_buf(); - let p = root.join(call_path).canonicalize(); - println!("{:?}", p); - p.ok() - } else { - let pb: PathBuf = call_path.into(); - println!("no base uri: {}", pb.display()); - pb.canonicalize().ok() - } -} - #[cfg(test)] mod tests { diff --git a/src/rules/issues.rs b/src/rules/issues.rs new file mode 100644 index 0000000..5e184d8 --- /dev/null +++ b/src/rules/issues.rs @@ -0,0 +1,143 @@ +use std::io::Write; + +use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range}; + +use crate::{ + lexer::print_range, + parser::Parser, + rules::{ + collect_labels::CollectLabels, + macro_args::{MacroArgProblem, MacroArgs}, + macro_variables::{MacroVarProblem, MacroVars}, + undefined_exec_call::{UndefinedExecCall, UndefinedExecCallProblem}, + }, +}; + +pub struct Issues { + missing_files: Vec, + macro_args: Vec, + macro_variables: Vec, + undefined_exec_call: Vec, +} + +impl Issues { + pub fn new() -> Self { + Self { + missing_files: Vec::new(), + macro_args: Vec::new(), + macro_variables: Vec::new(), + undefined_exec_call: Vec::new(), + } + } + + pub fn from_parser(parser: &Parser) -> Self { + for mf in parser.get_missing_files().iter() { + println!("MissingFile | {}", print_range(mf)); + } + + let collect_labels = CollectLabels::new(&parser); + let missing_callee = UndefinedExecCall::new(&parser, collect_labels.get_labels()); + let macro_args = MacroArgs::new(&parser); + let macro_vars = MacroVars::new(&parser); + + Self { + missing_files: parser.get_missing_files().clone(), + macro_args: macro_args.get_problems().clone(), + macro_variables: macro_vars.get_problems().clone(), + undefined_exec_call: missing_callee.get_problems().clone(), + } + } + + pub fn print_problems(&self, w: &mut W) -> Result<(), std::io::Error> { + for problem in self.macro_args.iter() { + writeln!(w, "{}", problem)?; + } + for problem in self.macro_variables.iter() { + writeln!(w, "{}", problem)?; + } + for problem in self.undefined_exec_call.iter() { + writeln!(w, "{}", problem)?; + } + for problem in self.missing_files.iter() { + writeln!(w, "{}", print_range(problem))?; + } + Ok(()) + } + + fn range_to_diagnostic_range(range: Range) -> Range { + Range { + start: Position { + line: range.start.line - 1, + character: range.start.character, + }, + end: Position { + line: range.end.line - 1, + character: range.end.character, + }, + } + } + + pub fn to_diagnostics(&self) -> Vec { + let mut diagnostics = Vec::new(); + for problem in self.macro_args.iter() { + let (range, message) = match problem { + MacroArgProblem::TooShort(range) => (*range, "macro arg too short".to_string()), + MacroArgProblem::Unused(range) => (*range, "macro arg unused".to_string()), + }; + let diagnostic = Diagnostic { + range: Self::range_to_diagnostic_range(range), + severity: Some(DiagnosticSeverity::HINT), + code: None, + code_description: None, + source: Some("madxls".to_string()), + message, + related_information: None, + tags: None, + data: None, + }; + diagnostics.push(diagnostic); + } + for problem in self.macro_variables.iter() { + let (range, message) = match problem { + MacroVarProblem::ExistWhileDef(range, _) => { + (*range, "macro var exist while def".to_string()) + } + MacroVarProblem::ExistsWhileCall(range, _) => { + (*range, "macro var exist while call".to_string()) + } + }; + let diagnostic = Diagnostic { + range: Self::range_to_diagnostic_range(range), + severity: Some(DiagnosticSeverity::WARNING), + code: None, + code_description: None, + source: Some("madxls".to_string()), + message, + related_information: None, + tags: None, + data: None, + }; + diagnostics.push(diagnostic); + } + for problem in self.undefined_exec_call.iter() { + let (range, message) = match problem { + UndefinedExecCallProblem::UndefinedExecCall(range) => { + (*range, "undefined exec call".to_string()) + } + }; + let diagnostic = Diagnostic { + range: Self::range_to_diagnostic_range(range), + severity: Some(DiagnosticSeverity::ERROR), + code: None, + code_description: None, + source: Some("madxls".to_string()), + message, + related_information: None, + tags: None, + data: None, + }; + diagnostics.push(diagnostic); + } + diagnostics + } +} diff --git a/src/rules/macro_args.rs b/src/rules/macro_args.rs index 5c4b8b4..fa58b4d 100644 --- a/src/rules/macro_args.rs +++ b/src/rules/macro_args.rs @@ -2,6 +2,7 @@ use std::fmt::Display; use crate::{lexer::CursorPosition, parser::Parser, visitor::Visitor}; +#[derive(Debug, Clone)] pub enum MacroArgProblem { TooShort(tower_lsp::lsp_types::Range), Unused(tower_lsp::lsp_types::Range), diff --git a/src/rules/macro_variables.rs b/src/rules/macro_variables.rs index b2f1de8..8f3edb4 100644 --- a/src/rules/macro_variables.rs +++ b/src/rules/macro_variables.rs @@ -8,6 +8,7 @@ use crate::{ visitor::Visitor, }; +#[derive(Debug, Clone)] pub enum MacroVarProblem { ExistWhileDef(Range, Range), ExistsWhileCall(Range, Range), diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 5a48340..bc9d34b 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -1,4 +1,5 @@ pub mod collect_labels; +pub mod issues; pub mod macro_args; -pub mod undefined_exec_call; pub mod macro_variables; +pub mod undefined_exec_call; diff --git a/src/rules/undefined_exec_call.rs b/src/rules/undefined_exec_call.rs index deb0967..4d5bb87 100644 --- a/src/rules/undefined_exec_call.rs +++ b/src/rules/undefined_exec_call.rs @@ -1,10 +1,19 @@ -use std::collections::HashSet; +use std::{collections::HashSet, fmt::Display}; -use crate::{lexer::CursorPosition, parser::Parser, visitor::Visitor}; +use crate::{ + lexer::{print_range, CursorPosition}, + parser::Parser, + visitor::Visitor, +}; + +#[derive(Debug, Clone)] +pub enum UndefinedExecCallProblem { + UndefinedExecCall(tower_lsp::lsp_types::Range), +} pub struct UndefinedExecCall<'a> { labels: &'a HashSet>, - problems: Vec<(CursorPosition, CursorPosition)>, + problems: Vec, } impl<'a> UndefinedExecCall<'a> { @@ -18,13 +27,22 @@ impl<'a> UndefinedExecCall<'a> { new_visitor } - pub fn check(&mut self, callee: &[u8], start: CursorPosition, end: CursorPosition) { + pub fn check( + &mut self, + callee: &[u8], + start: CursorPosition, + end: CursorPosition, + parser: &Parser, + ) { if !self.labels.contains(callee) { - self.problems.push((start, end)); + self.problems + .push(UndefinedExecCallProblem::UndefinedExecCall( + parser.lexer.cursor_range_to_text_range(&(start, end)), + )); } } - pub fn get_problems(&self) -> &Vec<(CursorPosition, CursorPosition)> { + pub fn get_problems(&self) -> &Vec { &self.problems } } @@ -34,7 +52,7 @@ impl<'a> Visitor for UndefinedExecCall<'a> { fn visit_exec(&mut self, exec_exp: &crate::parser::MadExec, parser: &Parser) { let callee = exec_exp.get_callee(); let callee_str = parser.get_element_bytes(&callee); - self.check(callee_str, callee.0, callee.1); + self.check(callee_str, callee.0, callee.1, parser); } fn visit_label(&mut self, _: &crate::parser::Label, _parser: &Parser) {} @@ -56,3 +74,13 @@ impl<'a> Visitor for UndefinedExecCall<'a> { fn visit_subparser(&mut self, _: &str, _: &crate::parser::Parser) {} } + +impl Display for UndefinedExecCallProblem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UndefinedExecCallProblem::UndefinedExecCall(range) => { + write!(f, "MissingCallee | {}", print_range(range)) + } + } + } +}