diff options
| author | Peter Pentchev <roam@ringlet.net> | 2024-09-19 18:21:12 +0300 |
|---|---|---|
| committer | Peter Pentchev <roam@ringlet.net> | 2024-09-19 23:31:02 +0300 |
| commit | cdf8d0aede346b1fdcf7b3ee27d72b144e01b7df (patch) | |
| tree | 3bc72e001a3c4fbf5e48492be5d05b1cfdb9c7bd | |
| parent | 46687ac6d2c5d604e72f4aaae1473c452cb45128 (diff) | |
| download | perlweeklychallenge-club-cdf8d0aede346b1fdcf7b3ee27d72b144e01b7df.tar.gz perlweeklychallenge-club-cdf8d0aede346b1fdcf7b3ee27d72b144e01b7df.tar.bz2 perlweeklychallenge-club-cdf8d0aede346b1fdcf7b3ee27d72b144e01b7df.zip | |
Add Peter Pentchev's Rust solutions to 287
| -rw-r--r-- | challenge-287/ppentchev/REUSE.toml | 6 | ||||
| -rw-r--r-- | challenge-287/ppentchev/docs/index.md | 12 | ||||
| -rw-r--r-- | challenge-287/ppentchev/rust/.gitignore | 7 | ||||
| -rw-r--r-- | challenge-287/ppentchev/rust/Cargo.lock | 501 | ||||
| -rw-r--r-- | challenge-287/ppentchev/rust/Cargo.toml | 24 | ||||
| -rwxr-xr-x | challenge-287/ppentchev/rust/run-clippy.sh | 70 | ||||
| -rw-r--r-- | challenge-287/ppentchev/rust/src/bin/ch-1.rs | 284 | ||||
| -rw-r--r-- | challenge-287/ppentchev/rust/src/bin/ch-2.rs | 117 | ||||
| -rw-r--r-- | challenge-287/ppentchev/tests/05-rust-ch-1.t | 35 | ||||
| -rw-r--r-- | challenge-287/ppentchev/tests/06-rust-ch-2.t | 39 |
10 files changed, 1095 insertions, 0 deletions
diff --git a/challenge-287/ppentchev/REUSE.toml b/challenge-287/ppentchev/REUSE.toml index c94dec6f95..90c0e1473a 100644 --- a/challenge-287/ppentchev/REUSE.toml +++ b/challenge-287/ppentchev/REUSE.toml @@ -11,3 +11,9 @@ path = "blog.txt" precedence = "aggregate" SPDX-FileCopyrightText = "Peter Pentchev <roam@ringlet.net>" SPDX-License-Identifier = "BSD-2-Clause" + +[[annotations]] +path = "rust/Cargo.lock" +precedence = "aggregate" +SPDX-FileCopyrightText = "Peter Pentchev <roam@ringlet.net>" +SPDX-License-Identifier = "BSD-2-Clause" diff --git a/challenge-287/ppentchev/docs/index.md b/challenge-287/ppentchev/docs/index.md index ec92fdf7d2..29dccbdba3 100644 --- a/challenge-287/ppentchev/docs/index.md +++ b/challenge-287/ppentchev/docs/index.md @@ -271,6 +271,11 @@ character into a `RunState` class. The `fix_runs()`, `fix_length()`, and `fix_missing()` functions are translated into the `fix-runs()`, `fix-length()`, and `fix-missing()` methods of the `Strength` class. +#### Rust + +Just as in the Raku solution, we encapsulate some of the functions within the `RunState` and +`Strength` structs. + ### Task 2: Valid Number #### Perl @@ -293,6 +298,12 @@ a non-empty, defined result is enough to determine whether the input was recogni Well, okay, since Raku grammars are actually regular expressions in disguise, one might argue that we did not go so far the other way then :) +#### Rust + +In the Rust solution we use [the nom parser combinators library][rust-nom] to build +a parser that, once again, returns nothing (all the `_p_*()` functions return +the `()` unit as the parsed result), but recognizes a number when it sees one. + ## Contact These solutions were written by [Peter Pentchev][roam]. @@ -304,3 +315,4 @@ This documentation is hosted at [Ringlet][ringlet-home]. [ringlet-home]: https://devel.ringlet.net/misc/perlweeklychallenge-club/287/ "This documentation at Ringlet" [pwc-287]: https://theweeklychallenge.org/blog/perl-weekly-challenge-287/ "The 287th Perl & Raku Weekly Challenge" +[rust-nom]: https://crates.io/crates/nom "nom, eating data byte by byte" diff --git a/challenge-287/ppentchev/rust/.gitignore b/challenge-287/ppentchev/rust/.gitignore new file mode 100644 index 0000000000..863eb2cc1b --- /dev/null +++ b/challenge-287/ppentchev/rust/.gitignore @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net> +# SPDX-License-Identifier: BSD-2-Clause + +.tox/ + +site/ +target/ diff --git a/challenge-287/ppentchev/rust/Cargo.lock b/challenge-287/ppentchev/rust/Cargo.lock new file mode 100644 index 0000000000..2edaa0c161 --- /dev/null +++ b/challenge-287/ppentchev/rust/Cargo.lock @@ -0,0 +1,501 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "perl-weekly-challenge-287" +version = "0.1.0" +dependencies = [ + "anyhow", + "nom", + "rstest", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rstest" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b423f0e62bdd61734b67cd21ff50871dfaeb9cc74f869dcd6af974fbcb19936" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e1711e7d14f74b12a58411c542185ef7fb7f2e7f8ee6e2940a883628522b42" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[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 = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] diff --git a/challenge-287/ppentchev/rust/Cargo.toml b/challenge-287/ppentchev/rust/Cargo.toml new file mode 100644 index 0000000000..b6b2a52377 --- /dev/null +++ b/challenge-287/ppentchev/rust/Cargo.toml @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net> +# SPDX-License-Identifier: BSD-2-Clause + +[package] +name = "perl-weekly-challenge-287" +version = "0.1.0" +rust-version = "1.58" +authors = ["Peter Pentchev <roam@ringlet.net>"] +edition = "2021" +description = "A solution to the Perl Weekly Challenge 287" +readme = "README.md" +repository = "https://github.com/ppentchev/perlweeklychallenge-club" +license = "BSD-2-Clause" +categories = ["algorithms"] +keywords = ["perl-weekly-challenge"] + +[dependencies] +anyhow = "1" +nom = "7.1.2" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" + +[dev-dependencies] +rstest = "0.22" diff --git a/challenge-287/ppentchev/rust/run-clippy.sh b/challenge-287/ppentchev/rust/run-clippy.sh new file mode 100755 index 0000000000..746742f2e4 --- /dev/null +++ b/challenge-287/ppentchev/rust/run-clippy.sh @@ -0,0 +1,70 @@ +#!/bin/sh +# +# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net> +# SPDX-License-Identifier: BSD-2-Clause + + +set -e + +def_cargo='cargo' + +usage() +{ + cat <<EOUSAGE +Usage: run-clippy.sh [-c cargo] [-n] + -c specify the Cargo command to use (default: $def_cargo) + -n also warn about lints in the Clippy "nursery" category +EOUSAGE +} + +unset run_nursery +cargo="$def_cargo" + +while getopts 'c:n' o; do + case "$o" in + c) + cargo="$OPTARG" + ;; + + n) + run_nursery=1 + ;; + + *) + usage 1>&2 + exit 1 + ;; + esac +done +shift "$((OPTIND - 1))" + +# The list of allowed and ignored checks is synced with Rust 1.76. +set -x +"$cargo" clippy \ + --tests \ + -- \ + -W warnings \ + -W future-incompatible \ + -W nonstandard-style \ + -W rust-2018-compatibility \ + -W rust-2018-idioms \ + -W rust-2021-compatibility \ + -W unused \ + -W clippy::restriction \ + -A clippy::blanket_clippy_restriction_lints \ + -A clippy::std_instead_of_alloc \ + -A clippy::std_instead_of_core \ + -A clippy::implicit_return \ + -A clippy::missing_trait_methods \ + -A clippy::mod_module_files \ + -A clippy::question_mark_used \ + -A clippy::ref_patterns \ + -A clippy::semicolon_outside_block \ + -A clippy::separated_literal_suffix \ + -A clippy::single_call_fn \ + -A clippy::self_named_module_files \ + -W clippy::pedantic \ + -A clippy::module_name_repetitions \ + -W clippy::cargo \ + ${run_nursery+-W clippy::nursery} \ + "$@" diff --git a/challenge-287/ppentchev/rust/src/bin/ch-1.rs b/challenge-287/ppentchev/rust/src/bin/ch-1.rs new file mode 100644 index 0000000000..aa295aca49 --- /dev/null +++ b/challenge-287/ppentchev/rust/src/bin/ch-1.rs @@ -0,0 +1,284 @@ +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +// SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net> +// SPDX-License-Identifier: BSD-2-Clause +//! PWC 287 task 1: Strong Password + +use std::collections::HashSet; +use std::env; +use std::io; +use std::process::{ExitCode, Termination}; + +use anyhow::{anyhow, Context, Result}; +use tracing::{debug, trace, Level}; + +/// The examples from the problem. +const TEST_STRINGS: [&str; 5] = ["a", "aB2", "PaaSW0rd", "PaaaSW0rd", "aaaaa"]; + +/// The minimum length for a password considered strong. +const DESIRED_LENGTH: usize = 6; + +/// The recognized character classes. +#[derive(Debug, PartialEq, Eq, Hash)] +enum CharClass { + /// A lowercase letter. + Lower, + + /// An uppercase letter. + Upper, + + /// A digit. + Digit, +} + +impl CharClass { + /// Examine a character, return a possible character class. + const fn from_char(value: char) -> Option<Self> { + if value.is_ascii_lowercase() { + Some(Self::Lower) + } else if value.is_ascii_uppercase() { + Some(Self::Upper) + } else if value.is_ascii_digit() { + Some(Self::Digit) + } else { + None + } + } +} + +/// The current state of examining the password string for length and character classes. +#[derive(Debug, Default)] +struct RunState { + /// The character classes found so far. + classes: HashSet<CharClass>, + + /// The runs collected so far. + runs: Vec<usize>, + + /// The last character seen. + last: char, + + /// The number of times the last character was seen. + seen: usize, +} + +impl RunState { + /// Process a single character, update the runs state and the missing classes. + #[allow(clippy::arithmetic_side_effects)] + #[allow(clippy::integer_division)] + fn update(mut self, value: char) -> Self { + if let Some(class) = CharClass::from_char(value) { + self.classes.insert(class); + } + + if value == self.last { + self.seen += 1; + } else { + if self.seen >= 3 { + self.runs.push(self.seen); + } + self.last = value; + self.seen = 1; + } + + self + } + + /// Record the count of the last run of characters if needed. + fn update_finish(mut self) -> Self { + if self.seen >= 3 { + self.runs.push(self.seen); + } + self + } +} + +/// The various attributes determining the password strength. +#[derive(Debug)] +struct Strength { + /// The number of actions taken to increase the password strength. + actions: usize, + + /// The current length of the password. + length: usize, + + /// The number of missing character classes. + missing: usize, + + /// The respective lengths of the too-long runs of the same character. + runs: Vec<usize>, +} + +impl Strength { + /// Diagnostic output. + fn diag(&self, tag: &str) { + debug!( + "state {tag}: actions {actions}, length {length}, missing {missing}, runs {runs:?}", + actions = self.actions, + length = self.length, + missing = self.missing, + runs = self.runs + ); + } + + /// Determine what changes need to be made to break successive runs of the same character. + #[allow(clippy::integer_division)] + #[allow(clippy::integer_division_remainder_used)] + fn fix_runs(self) -> Result<Self> { + let taken: usize = self.runs.into_iter().map(|run| run / 3).sum(); + let actions = if taken > 0 { + self.actions.checked_add(taken).ok_or_else(|| { + anyhow!( + "Could not add {taken} new actions to {current}", + current = self.actions + ) + })? + } else { + self.actions + }; + let missing = self.missing.saturating_sub(taken); + Ok(Self { + actions, + missing, + runs: vec![], + ..self + }) + } + + /// Determine what changes need to be made to bring the string to the desired length. + fn fix_length(self) -> Result<Self> { + let taken = DESIRED_LENGTH.saturating_sub(self.length); + let actions = if taken > 0 { + self.actions.checked_add(taken).ok_or_else(|| { + anyhow!( + "Could not add {taken} new actions to {current}", + current = self.actions + ) + })? + } else { + self.actions + }; + let length = if taken > 0 { + DESIRED_LENGTH + } else { + self.length + }; + let missing = self.missing.saturating_sub(taken); + Ok(Self { + actions, + length, + missing, + ..self + }) + } + + /// Determine what changes need to be made to represent all the required character classes. + fn fix_missing(self) -> Result<Self> { + let taken = self.missing; + let actions = if taken > 0 { + self.actions.checked_add(taken).ok_or_else(|| { + anyhow!( + "Could not add {taken} new actions to {current}", + current = self.actions + ) + })? + } else { + self.actions + }; + Ok(Self { + actions, + missing: 0, + ..self + }) + } +} + +/// The result of the main program or an invoked subcommand. +enum MainResult { + /// Everything seems fine. + Ok, +} + +impl Termination for MainResult { + fn report(self) -> ExitCode { + match self { + Self::Ok => ExitCode::SUCCESS, + } + } +} + +/// Perform the initial analysis on the password: length, runs, missing classes. +fn _examine_password(password: &str) -> Result<Strength> { + let length = password.len(); + let state = password + .chars() + .fold(RunState::default(), RunState::update) + .update_finish(); + trace!(?state); + + let missing = 3_usize.checked_sub(state.classes.len()).ok_or_else(|| { + anyhow!( + "Could not subtract {count} from 3", + count = state.classes.len() + ) + })?; + + Ok(Strength { + actions: 0, + length, + missing, + runs: state.runs, + }) +} + +/// Calculate the task-dependent result. +#[tracing::instrument] +fn strong_password(password: &str) -> Result<usize> { + debug!("Examining a password: {password:?}"); + let st_init = _examine_password(password)?; + st_init.diag("init"); + + let st_runs = st_init.fix_runs()?; + st_runs.diag("runs"); + + let st_length = st_runs.fix_length()?; + st_length.diag("length"); + + let st_missing = st_length.fix_missing()?; + st_missing.diag("missing"); + + Ok(st_missing.actions) +} + +/// Initialize the logging subsystem provided by the `tracing` library. +fn setup_tracing() -> Result<()> { + let quiet = env::var("PWC_QUIET").unwrap_or_else(|_| String::new()) == "1"; + let sub = tracing_subscriber::fmt() + .without_time() + .with_max_level(if quiet { Level::INFO } else { Level::TRACE }) + .with_writer(io::stderr) + .finish(); + #[allow(clippy::absolute_paths)] + tracing::subscriber::set_global_default(sub).context("Could not initialize the tracing logger") +} + +/// The main program - parse the environment settings, do something about it. +#[allow(clippy::print_stdout)] +fn main() -> Result<MainResult> { + setup_tracing()?; + if env::var("PWC_FROM_STDIN").unwrap_or_else(|_| String::new()) == "1" { + let mut line = String::new(); + io::stdin() + .read_line(&mut line) + .context("Could not read a line from the standard input stream")?; + println!( + "{res}", + res = strong_password(line.trim_end_matches(&['\r', '\n']))? + ); + } else { + for password in TEST_STRINGS { + println!("{res}", res = strong_password(password)?); + } + } + Ok(MainResult::Ok) +} diff --git a/challenge-287/ppentchev/rust/src/bin/ch-2.rs b/challenge-287/ppentchev/rust/src/bin/ch-2.rs new file mode 100644 index 0000000000..ed383ae837 --- /dev/null +++ b/challenge-287/ppentchev/rust/src/bin/ch-2.rs @@ -0,0 +1,117 @@ +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +// SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net> +// SPDX-License-Identifier: BSD-2-Clause +//! PWC 287 task 2: Valid Number + +use std::env; +use std::io; +use std::process::{ExitCode, Termination}; + +use anyhow::{Context, Result}; +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::one_of, + combinator::{all_consuming, opt}, + multi::{many0, many1}, + sequence::tuple, + IResult, +}; +use tracing::debug; + +/// The examples from the problem. +const TEST_NUMBERS: [&str; 7] = ["1", "a", ".", "1.2e4.2", "-1.", "+1E-8", ".44"]; + +/// The result of the main program or an invoked subcommand. +enum MainResult { + /// Everything seems fine. + Ok, +} + +impl Termination for MainResult { + fn report(self) -> ExitCode { + match self { + Self::Ok => ExitCode::SUCCESS, + } + } +} + +/// Recognize a plus or minus sign at the start of the mantissa or the exponent. +fn _p_sign(input: &str) -> IResult<&str, ()> { + let (r_input, _) = one_of("+-")(input)?; + Ok((r_input, ())) +} + +/// Recognize an ASCII decimal digit. +fn _p_digit(input: &str) -> IResult<&str, ()> { + let (r_input, _) = one_of("0123456789")(input)?; + Ok((r_input, ())) +} + +/// Recognize a decimal dot maybe followed by some digits. +fn _p_large_fractional_part(input: &str) -> IResult<&str, ()> { + let (r_input, _) = tuple((tag("."), many0(_p_digit)))(input)?; + Ok((r_input, ())) +} + +/// Recognize a number with some digits before the optional decimal dot, etc. +fn _p_large_number(input: &str) -> IResult<&str, ()> { + let (r_input, _) = tuple((many1(_p_digit), opt(_p_large_fractional_part)))(input)?; + Ok((r_input, ())) +} + +/// Recognize a number between 0 and 1: a decimal dot and some digits. +fn _p_small_number(input: &str) -> IResult<&str, ()> { + let (r_input, _) = tuple((tag("."), many1(_p_digit)))(input)?; + Ok((r_input, ())) +} + +/// Recognize a valid mantissa: sign, digits, decimal dot, etc. +fn _p_mantissa(input: &str) -> IResult<&str, ()> { + let (r_input, _) = tuple((opt(_p_sign), alt((_p_large_number, _p_small_number))))(input)?; + Ok((r_input, ())) +} + +/// Recognize a valid `[Ee][+-]?{digits}` exponent, return the unit value on success. +fn _p_exponent(input: &str) -> IResult<&str, ()> { + let (r_input, _) = tuple((one_of("Ee"), opt(_p_sign), many1(_p_digit)))(input)?; |
