build(rust): vendor googletest crate #76
98
Cargo.lock
generated
98
Cargo.lock
generated
@@ -2,6 +2,15 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -62,6 +71,12 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@@ -93,7 +108,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -114,6 +129,29 @@ version = "3.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "googletest"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6b5e2f2b556b7b90297a5a35c8267dd43a537923d2b329beefdba2b4ec19d94"
|
||||||
|
dependencies = [
|
||||||
|
"googletest_macro",
|
||||||
|
"num-traits",
|
||||||
|
"regex",
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "googletest_macro"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ae6abc96141edd26bf5aeec0f119c129c44de3ced09e5073711a02cb74725d0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
@@ -152,6 +190,15 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
|||||||
name = "monorepo"
|
name = "monorepo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell_polyfill"
|
name = "once_cell_polyfill"
|
||||||
version = "1.70.2"
|
version = "1.70.2"
|
||||||
@@ -176,6 +223,35 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rnix"
|
name = "rnix"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -203,6 +279,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.229"
|
version = "1.0.229"
|
||||||
@@ -230,7 +312,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -256,6 +338,17 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "3.0.6"
|
version = "3.0.6"
|
||||||
@@ -305,6 +398,7 @@ name = "xatu"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"googletest",
|
||||||
"monorepo",
|
"monorepo",
|
||||||
"rnix",
|
"rnix",
|
||||||
"rowan",
|
"rowan",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ resolver = "3"
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
clap = "4"
|
clap = "4"
|
||||||
|
googletest = "0.14"
|
||||||
monorepo = { path = "src/monorepo" }
|
monorepo = { path = "src/monorepo" }
|
||||||
rnix = "=0.14.0"
|
rnix = "=0.14.0"
|
||||||
rowan = "0.16"
|
rowan = "0.16"
|
||||||
|
|||||||
@@ -16,3 +16,6 @@ monorepo = { workspace = true }
|
|||||||
rnix = { workspace = true }
|
rnix = { workspace = true }
|
||||||
rowan = { workspace = true }
|
rowan = { workspace = true }
|
||||||
status = { workspace = true }
|
status = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
googletest = { workspace = true }
|
||||||
|
|||||||
24
third_party/crates/BUILD.bazel
vendored
24
third_party/crates/BUILD.bazel
vendored
@@ -43,6 +43,18 @@ alias(
|
|||||||
tags = ["manual"],
|
tags = ["manual"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
alias(
|
||||||
|
name = "googletest-0.14.3",
|
||||||
|
actual = "//third_party/crates/googletest-0.14.3:googletest",
|
||||||
|
tags = ["manual"],
|
||||||
|
)
|
||||||
|
|
||||||
|
alias(
|
||||||
|
name = "googletest",
|
||||||
|
actual = "//third_party/crates/googletest-0.14.3:googletest",
|
||||||
|
tags = ["manual"],
|
||||||
|
)
|
||||||
|
|
||||||
alias(
|
alias(
|
||||||
name = "rnix-0.14.0",
|
name = "rnix-0.14.0",
|
||||||
actual = "//third_party/crates/rnix-0.14.0:rnix",
|
actual = "//third_party/crates/rnix-0.14.0:rnix",
|
||||||
@@ -55,6 +67,18 @@ alias(
|
|||||||
tags = ["manual"],
|
tags = ["manual"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
alias(
|
||||||
|
name = "rowan-0.16.1",
|
||||||
|
actual = "//third_party/crates/rowan-0.16.1:rowan",
|
||||||
|
tags = ["manual"],
|
||||||
|
)
|
||||||
|
|
||||||
|
alias(
|
||||||
|
name = "rowan",
|
||||||
|
actual = "//third_party/crates/rowan-0.16.1:rowan",
|
||||||
|
tags = ["manual"],
|
||||||
|
)
|
||||||
|
|
||||||
alias(
|
alias(
|
||||||
name = "serde-1.0.229",
|
name = "serde-1.0.229",
|
||||||
actual = "//third_party/crates/serde-1.0.229:serde",
|
actual = "//third_party/crates/serde-1.0.229:serde",
|
||||||
|
|||||||
1
third_party/crates/aho-corasick-1.1.5/.cargo-checksum.json
vendored
Normal file
1
third_party/crates/aho-corasick-1.1.5/.cargo-checksum.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"fa452b0920e10ae97397f4cb336b629f7cbe5de0bed8fd8046ce8c137c06e2a4",".github/FUNDING.yml":"0c65f392d32a8639ba7986bbb42ca124505b462122382f314c89d84c95dd27f1",".github/dependabot.yml":"ea498dc2f28b89e80cf42a1349ac48ba9a9cc4d53f6cb21333481a4611f95abb",".github/workflows/ci.yml":"2d9409bedd406551a2e7e62158f3826c54c38a15d35e2a1a660e47499ea1999e",".vim/coc-settings.json":"8237c8f41db352b0d83f1bb10a60bc2f60f56f3234afbf696b4075c8d4d62d9b","COPYING":"01c266bced4a434da0051174d6bee16a4c82cf634e2679b6155d40d75012390f","Cargo.lock":"9b5026fc4b42d1470b586a636f7c0e843ca9a056f4137727bf7ba36baaa9c9b0","Cargo.toml":"7ff243aed78547f286729c3c60d97577fbc685546120263548d7c4f2a3884bc1","Cargo.toml.orig":"6c8341535cb4f736c1ac6b18292df2c572ccb18740bb9325e42b22c67f9e27da","DESIGN.md":"59c960e1b73b1d7fb41e4df6c0c1b1fcf44dd2ebc8a349597a7d0595f8cb5130","LICENSE-MIT":"0f96a83840e146e43c0ec96a22ec1f392e0680e6c1226e6f3ba87e0740af850f","README.md":"afc4d559a98cf190029af0bf320fc0022725e349cd2a303aac860254e28f3c53","UNLICENSE":"7e12e5df4bae12cb21581ba157ced20e1986a0508dd10d0e8a4ab9a4cf94e85c","rustfmt.toml":"1ca600239a27401c4a43f363cf3f38183a212affc1f31bff3ae93234bbaec228","src/ahocorasick.rs":"c699c07df70be45c666e128509ad571a7649d2073e4ae16ac1efd6793c9c6890","src/automaton.rs":"22258a3e118672413119f8f543a9b912cce954e63524575c0ebfdf9011f9c2dd","src/dfa.rs":"197075923eb9d760a552f4e8652310fd4f657736613a9b1444ae05ef5d525da3","src/lib.rs":"66dea84d227f269b2f14ecc8109a97e96245b56c22eef0e8ce03b2343b8d6e66","src/macros.rs":"c6c52ae05b24433cffaca7b78b3645d797862c5d5feffddf9f54909095ed6e05","src/nfa/contiguous.rs":"f435c131ce84927e5600109722d006533ea21442dddaf18e03286d8caed82389","src/nfa/mod.rs":"ee7b3109774d14bbad5239c16bb980dd6b8185ec136d94fbaf2f0dc27d5ffa15","src/nfa/noncontiguous.rs":"de94f02b04efd8744fb096759a8897c22012b0e0ca3ace161fd87c71befefe04","src/packed/api.rs":"d555a8c7a6da33b75f1f90e21ee970629f92f7bddd4b408b78656cd93fe1a3d6","src/packed/ext.rs":"66be06fde8558429da23a290584d4b9fae665bf64c2578db4fe5f5f3ee864869","src/packed/mod.rs":"0020cd6f07ba5c8955923a9516d7f758864260eda53a6b6f629131c45ddeec62","src/packed/pattern.rs":"0e4bca57d4b941495d31fc8246ad32904eed0cd89e3cda732ad35f4deeba3bef","src/packed/rabinkarp.rs":"403146eb1d838a84601d171393542340513cd1ee7ff750f2372161dd47746586","src/packed/teddy/README.md":"3a43194b64e221543d885176aba3beb1224a927385a20eca842daf6b0ea2f342","src/packed/teddy/builder.rs":"08ec116a4a842a2bb1221d296a2515ef3672c54906bed588fb733364c07855d3","src/packed/teddy/generic.rs":"ea252ab05b32cea7dd9d71e332071d243db7dd0362e049252a27e5881ba2bf39","src/packed/teddy/mod.rs":"17d741f7e2fb9dbac5ba7d1bd4542cf1e35e9f146ace728e23fe6bbed20028b2","src/packed/tests.rs":"8e2f56eb3890ed3876ecb47d3121996e416563127b6430110d7b516df3f83b4b","src/packed/vector.rs":"70c325cfa6f7c5c4c9a6af7b133b75a29e65990a7fe0b9a4c4ce3c3d5a0fe587","src/tests.rs":"c68192ab97b6161d0d6ee96fefd80cc7d14e4486ddcd8d1f82b5c92432c24ed5","src/transducer.rs":"02daa33a5d6dac41dcfd67f51df7c0d4a91c5131c781fb54c4de3520c585a6e1","src/util/alphabet.rs":"6dc22658a38deddc0279892035b18870d4585069e35ba7c7e649a24509acfbcc","src/util/buffer.rs":"f9e37f662c46c6ecd734458dedbe76c3bb0e84a93b6b0117c0d4ad3042413891","src/util/byte_frequencies.rs":"2fb85b381c038c1e44ce94294531cdcd339dca48b1e61f41455666e802cbbc9e","src/util/debug.rs":"ab301ad59aa912529cb97233a54a05914dd3cb2ec43e6fec7334170b97ac5998","src/util/error.rs":"ecccd60e7406305023efcc6adcc826eeeb083ab8f7fbfe3d97469438cd4c4e5c","src/util/int.rs":"e264e6abebf5622b59f6500210773db36048371c4e509c930263334095959a52","src/util/mod.rs":"7ab28d11323ecdbd982087f32eb8bceeee84f1a2583f3aae27039c36d58cf12c","src/util/prefilter.rs":"183e32aa9951d9957f89062e4a6ae7235df7060722a3c91995a3d36db5a98111","src/util/primitives.rs":"f89f3fa1d8db4e37de9ca767c6d05e346404837cade6d063bba68972fafa610b","src/util/remapper.rs":"9f12d911583a325c11806eeceb46d0dfec863cfcfa241aed84d31af73da746e5","src/util/search.rs":"ac16607e69a3ff24d27c0680dc9afcd5bb7b7cb272179d5475300a2035fdf7e3","src/util/special.rs":"7d2f9cb9dd9771f59816e829b2d96b1239996f32939ba98764e121696c52b146"},"package":"c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"}
|
||||||
6
third_party/crates/aho-corasick-1.1.5/.cargo_vcs_info.json
vendored
Normal file
6
third_party/crates/aho-corasick-1.1.5/.cargo_vcs_info.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"git": {
|
||||||
|
"sha1": "5178060ce73d91938f8582d0360e3be031380440"
|
||||||
|
},
|
||||||
|
"path_in_vcs": ""
|
||||||
|
}
|
||||||
1
third_party/crates/aho-corasick-1.1.5/.github/FUNDING.yml
vendored
Normal file
1
third_party/crates/aho-corasick-1.1.5/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
github: [BurntSushi]
|
||||||
14
third_party/crates/aho-corasick-1.1.5/.github/dependabot.yml
vendored
Normal file
14
third_party/crates/aho-corasick-1.1.5/.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: semiannually
|
||||||
|
open-pull-requests-limit: 1
|
||||||
|
cooldown:
|
||||||
|
default-days: 30
|
||||||
|
groups:
|
||||||
|
actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
154
third_party/crates/aho-corasick-1.1.5/.github/workflows/ci.yml
vendored
Normal file
154
third_party/crates/aho-corasick-1.1.5/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
name: ci
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
schedule:
|
||||||
|
- cron: '00 01 * * *'
|
||||||
|
|
||||||
|
# The section is needed to drop write-all permissions that are granted on
|
||||||
|
# `schedule` event. By specifying any permission explicitly all others are set
|
||||||
|
# to none. By using the principle of least privilege the damage a compromised
|
||||||
|
# workflow can do (because of an injection or compromised third party tool or
|
||||||
|
# action) is restricted. Currently the worklow doesn't need any additional
|
||||||
|
# permission except for pulling the code. Adding labels to issues, commenting
|
||||||
|
# on pull-requests, etc. may need additional permissions:
|
||||||
|
#
|
||||||
|
# Syntax for this section:
|
||||||
|
# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||||
|
#
|
||||||
|
# Reference for how to assign permissions on a job-by-job basis:
|
||||||
|
# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs
|
||||||
|
#
|
||||||
|
# Reference for available permissions that we can enable if needed:
|
||||||
|
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
|
||||||
|
permissions:
|
||||||
|
# to fetch code (actions/checkout)
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: test
|
||||||
|
env:
|
||||||
|
# For some builds, we use cross to test on 32-bit and big-endian
|
||||||
|
# systems.
|
||||||
|
CARGO: cargo
|
||||||
|
# When CARGO is set to CROSS, TARGET is set to `--target matrix.target`.
|
||||||
|
# Note that we only use cross on Linux, so setting a target on a
|
||||||
|
# different OS will just use normal cargo.
|
||||||
|
TARGET:
|
||||||
|
# Bump this as appropriate. We pin to a version to make sure CI
|
||||||
|
# continues to work as cross releases in the past have broken things
|
||||||
|
# in subtle ways.
|
||||||
|
CROSS_VERSION: v0.2.5
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- build: pinned
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: 1.60.0
|
||||||
|
- build: stable
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
- build: stable-x86
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: i686-unknown-linux-gnu
|
||||||
|
- build: stable-aarch64
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
- build: stable-powerpc64
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: powerpc64-unknown-linux-gnu
|
||||||
|
- build: stable-s390x
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: s390x-unknown-linux-gnu
|
||||||
|
- build: beta
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: beta
|
||||||
|
- build: nightly
|
||||||
|
os: ubuntu-latest
|
||||||
|
rust: nightly
|
||||||
|
- build: macos
|
||||||
|
os: macos-latest
|
||||||
|
rust: stable
|
||||||
|
- build: win-msvc
|
||||||
|
os: windows-latest
|
||||||
|
rust: stable
|
||||||
|
- build: win-gnu
|
||||||
|
os: windows-latest
|
||||||
|
rust: stable-x86_64-gnu
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
- name: Install and configure Cross
|
||||||
|
if: matrix.os == 'ubuntu-latest' && matrix.target != ''
|
||||||
|
run: |
|
||||||
|
# In the past, new releases of 'cross' have broken CI. So for now, we
|
||||||
|
# pin it. We also use their pre-compiled binary releases because cross
|
||||||
|
# has over 100 dependencies and takes a bit to compile.
|
||||||
|
dir="$RUNNER_TEMP/cross-download"
|
||||||
|
mkdir "$dir"
|
||||||
|
echo "$dir" >> $GITHUB_PATH
|
||||||
|
cd "$dir"
|
||||||
|
curl -LO "https://github.com/cross-rs/cross/releases/download/$CROSS_VERSION/cross-x86_64-unknown-linux-musl.tar.gz"
|
||||||
|
tar xf cross-x86_64-unknown-linux-musl.tar.gz
|
||||||
|
|
||||||
|
# We used to install 'cross' from master, but it kept failing. So now
|
||||||
|
# we build from a known-good version until 'cross' becomes more stable
|
||||||
|
# or we find an alternative. Notably, between v0.2.1 and current
|
||||||
|
# master (2022-06-14), the number of Cross's dependencies has doubled.
|
||||||
|
echo "CARGO=cross" >> $GITHUB_ENV
|
||||||
|
echo "TARGET=--target ${{ matrix.target }}" >> $GITHUB_ENV
|
||||||
|
- name: Show command used for Cargo
|
||||||
|
run: |
|
||||||
|
echo "cargo command is: ${{ env.CARGO }}"
|
||||||
|
echo "target flag is: ${{ env.TARGET }}"
|
||||||
|
- name: Show CPU info for debugging
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
run: lscpu
|
||||||
|
# See: https://github.com/rust-lang/regex/blob/a2887636930156023172e4b376a6febad4e49120/.github/workflows/ci.yml#L145-L163
|
||||||
|
- name: Pin memchr to 2.6.2
|
||||||
|
if: matrix.build == 'pinned'
|
||||||
|
run: |
|
||||||
|
cargo update -p memchr --precise 2.6.2
|
||||||
|
cargo update -p log --precise 0.4.20
|
||||||
|
- run: ${{ env.CARGO }} build --verbose $TARGET
|
||||||
|
- run: ${{ env.CARGO }} doc --verbose $TARGET
|
||||||
|
- run: ${{ env.CARGO }} test --verbose $TARGET
|
||||||
|
- run: ${{ env.CARGO }} test --lib --verbose --no-default-features --features std,perf-literal $TARGET
|
||||||
|
- run: ${{ env.CARGO }} test --lib --verbose --no-default-features $TARGET
|
||||||
|
- run: ${{ env.CARGO }} test --lib --verbose --no-default-features --features std $TARGET
|
||||||
|
- run: ${{ env.CARGO }} test --lib --verbose --no-default-features --features perf-literal $TARGET
|
||||||
|
- run: ${{ env.CARGO }} test --lib --verbose --no-default-features --features std,perf-literal,logging $TARGET
|
||||||
|
- if: matrix.build == 'nightly'
|
||||||
|
run: ${{ env.CARGO }} build --manifest-path aho-corasick-debug/Cargo.toml $TARGET
|
||||||
|
|
||||||
|
rustfmt:
|
||||||
|
name: rustfmt
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
components: rustfmt
|
||||||
|
- name: Check formatting
|
||||||
|
run: |
|
||||||
|
cargo fmt --all -- --check
|
||||||
12
third_party/crates/aho-corasick-1.1.5/.vim/coc-settings.json
vendored
Normal file
12
third_party/crates/aho-corasick-1.1.5/.vim/coc-settings.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer.linkedProjects": [
|
||||||
|
"aho-corasick-debug/Cargo.toml",
|
||||||
|
"benchmarks/engines/rust-aho-corasick/Cargo.toml",
|
||||||
|
"benchmarks/engines/rust-daachorse/Cargo.toml",
|
||||||
|
"benchmarks/engines/rust-jetscii/Cargo.toml",
|
||||||
|
"benchmarks/engines/naive/Cargo.toml",
|
||||||
|
"benchmarks/shared/Cargo.toml",
|
||||||
|
"fuzz/Cargo.toml",
|
||||||
|
"Cargo.toml"
|
||||||
|
]
|
||||||
|
}
|
||||||
117
third_party/crates/aho-corasick-1.1.5/BUILD.bazel
vendored
Normal file
117
third_party/crates/aho-corasick-1.1.5/BUILD.bazel
vendored
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
###############################################################################
|
||||||
|
# @generated
|
||||||
|
# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To
|
||||||
|
# regenerate this file, run the following:
|
||||||
|
#
|
||||||
|
# bazel run @@//third_party:vendor_crates
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
load("@rules_rust//cargo:defs.bzl", "cargo_toml_env_vars")
|
||||||
|
load("@rules_rust//rust:defs.bzl", "rust_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
cargo_toml_env_vars(
|
||||||
|
name = "cargo_toml_env_vars",
|
||||||
|
src = "Cargo.toml",
|
||||||
|
)
|
||||||
|
|
||||||
|
rust_library(
|
||||||
|
name = "aho_corasick",
|
||||||
|
srcs = glob(
|
||||||
|
include = ["**/*.rs"],
|
||||||
|
allow_empty = False,
|
||||||
|
),
|
||||||
|
compile_data = glob(
|
||||||
|
include = ["**"],
|
||||||
|
allow_empty = True,
|
||||||
|
exclude = [
|
||||||
|
"**/* *",
|
||||||
|
".tmp_git_root/**/*",
|
||||||
|
"BUILD",
|
||||||
|
"BUILD.bazel",
|
||||||
|
"WORKSPACE",
|
||||||
|
"WORKSPACE.bazel",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
crate_features = [
|
||||||
|
"perf-literal",
|
||||||
|
"std",
|
||||||
|
],
|
||||||
|
crate_root = "src/lib.rs",
|
||||||
|
edition = "2021",
|
||||||
|
rustc_env_files = [
|
||||||
|
":cargo_toml_env_vars",
|
||||||
|
],
|
||||||
|
rustc_flags = [
|
||||||
|
"--cap-lints=allow",
|
||||||
|
],
|
||||||
|
tags = [
|
||||||
|
"cargo-bazel",
|
||||||
|
"crate-name=aho-corasick",
|
||||||
|
"manual",
|
||||||
|
"noclippy",
|
||||||
|
"norustfmt",
|
||||||
|
],
|
||||||
|
target_compatible_with = select({
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-darwin": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-ios": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-ios-macabi": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-ios-sim": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-linux-android": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-pc-windows-msvc": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-fuchsia": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-nixos-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-none": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-nto-qnx710": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-uefi": [],
|
||||||
|
"@rules_rust//rust/platform:arm-unknown-linux-gnueabi": [],
|
||||||
|
"@rules_rust//rust/platform:arm-unknown-linux-musleabi": [],
|
||||||
|
"@rules_rust//rust/platform:armv7-linux-androideabi": [],
|
||||||
|
"@rules_rust//rust/platform:armv7-unknown-linux-gnueabi": [],
|
||||||
|
"@rules_rust//rust/platform:i686-apple-darwin": [],
|
||||||
|
"@rules_rust//rust/platform:i686-linux-android": [],
|
||||||
|
"@rules_rust//rust/platform:i686-pc-windows-msvc": [],
|
||||||
|
"@rules_rust//rust/platform:i686-unknown-freebsd": [],
|
||||||
|
"@rules_rust//rust/platform:i686-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:loongarch64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:mips-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:powerpc-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:riscv32imac-unknown-none-elf": [],
|
||||||
|
"@rules_rust//rust/platform:riscv32imc-unknown-none-elf": [],
|
||||||
|
"@rules_rust//rust/platform:riscv64gc-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:riscv64gc-unknown-none-elf": [],
|
||||||
|
"@rules_rust//rust/platform:s390x-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:sparc64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:sparc64-unknown-netbsd": [],
|
||||||
|
"@rules_rust//rust/platform:sparc64-unknown-openbsd": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv6m-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv7em-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv7em-none-eabihf": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv7m-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv8m.main-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv8m.main-none-eabihf": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-unknown-emscripten": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-unknown-unknown": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-wasip1": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-wasip1-threads": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-wasip2": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-apple-darwin": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-apple-ios": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-apple-ios-macabi": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-linux-android": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-pc-windows-msvc": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-freebsd": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-fuchsia": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-none": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-uefi": [],
|
||||||
|
"//conditions:default": ["@platforms//:incompatible"],
|
||||||
|
}),
|
||||||
|
version = "1.1.5",
|
||||||
|
deps = [
|
||||||
|
"//third_party/crates/memchr-2.8.3:memchr",
|
||||||
|
],
|
||||||
|
)
|
||||||
3
third_party/crates/aho-corasick-1.1.5/COPYING
vendored
Normal file
3
third_party/crates/aho-corasick-1.1.5/COPYING
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
This project is dual-licensed under the Unlicense and MIT licenses.
|
||||||
|
|
||||||
|
You may use this code under the terms of either license.
|
||||||
30
third_party/crates/aho-corasick-1.1.5/Cargo.lock
generated
vendored
Normal file
30
third_party/crates/aho-corasick-1.1.5/Cargo.lock
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.5"
|
||||||
|
dependencies = [
|
||||||
|
"doc-comment",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "doc-comment"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
80
third_party/crates/aho-corasick-1.1.5/Cargo.toml
vendored
Normal file
80
third_party/crates/aho-corasick-1.1.5/Cargo.toml
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||||
|
#
|
||||||
|
# When uploading crates to the registry Cargo will automatically
|
||||||
|
# "normalize" Cargo.toml files for maximal compatibility
|
||||||
|
# with all versions of Cargo and also rewrite `path` dependencies
|
||||||
|
# to registry (e.g., crates.io) dependencies.
|
||||||
|
#
|
||||||
|
# If you are reading this file be aware that the original Cargo.toml
|
||||||
|
# will likely look very different (and much more reasonable).
|
||||||
|
# See Cargo.toml.orig for the original contents.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.60.0"
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.5"
|
||||||
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
|
build = false
|
||||||
|
exclude = [
|
||||||
|
"/aho-corasick-debug",
|
||||||
|
"/benchmarks",
|
||||||
|
"/tmp",
|
||||||
|
]
|
||||||
|
autolib = false
|
||||||
|
autobins = false
|
||||||
|
autoexamples = false
|
||||||
|
autotests = false
|
||||||
|
autobenches = false
|
||||||
|
description = "Fast multiple substring searching."
|
||||||
|
homepage = "https://github.com/BurntSushi/aho-corasick"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = [
|
||||||
|
"string",
|
||||||
|
"search",
|
||||||
|
"text",
|
||||||
|
"pattern",
|
||||||
|
"multi",
|
||||||
|
]
|
||||||
|
categories = ["text-processing"]
|
||||||
|
license = "Unlicense OR MIT"
|
||||||
|
repository = "https://github.com/BurntSushi/aho-corasick"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = [
|
||||||
|
"--cfg",
|
||||||
|
"docsrs",
|
||||||
|
"--generate-link-to-definition",
|
||||||
|
]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = [
|
||||||
|
"std",
|
||||||
|
"perf-literal",
|
||||||
|
]
|
||||||
|
logging = ["dep:log"]
|
||||||
|
perf-literal = ["dep:memchr"]
|
||||||
|
std = ["memchr?/std"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "aho_corasick"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies.log]
|
||||||
|
version = "0.4.17"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.memchr]
|
||||||
|
version = "2.4.0"
|
||||||
|
optional = true
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[dev-dependencies.doc-comment]
|
||||||
|
version = "0.3.3"
|
||||||
|
|
||||||
|
[profile.bench]
|
||||||
|
debug = 2
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = 2
|
||||||
74
third_party/crates/aho-corasick-1.1.5/Cargo.toml.orig
generated
vendored
Normal file
74
third_party/crates/aho-corasick-1.1.5/Cargo.toml.orig
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
[package]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.5" #:version
|
||||||
|
authors = ["Andrew Gallant <jamslam@gmail.com>"]
|
||||||
|
description = "Fast multiple substring searching."
|
||||||
|
homepage = "https://github.com/BurntSushi/aho-corasick"
|
||||||
|
repository = "https://github.com/BurntSushi/aho-corasick"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["string", "search", "text", "pattern", "multi"]
|
||||||
|
license = "Unlicense OR MIT"
|
||||||
|
categories = ["text-processing"]
|
||||||
|
autotests = false
|
||||||
|
exclude = ["/aho-corasick-debug", "/benchmarks", "/tmp"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.60.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "aho_corasick"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std", "perf-literal"]
|
||||||
|
std = ["memchr?/std"]
|
||||||
|
|
||||||
|
# Enables prefilter optimizations that depend on external crates.
|
||||||
|
perf-literal = ["dep:memchr"]
|
||||||
|
|
||||||
|
# Enable logging via the 'log' crate. This is useful for seeing messages about
|
||||||
|
# internal decisions and metrics. For example, how the choice of the internal
|
||||||
|
# Aho-Corasick implementation is used or the heap usage of an automaton.
|
||||||
|
logging = ["dep:log"]
|
||||||
|
|
||||||
|
# Provides a trait impl for fst::Automaton for nfa::noncontiguous::NFA,
|
||||||
|
# nfa::contiguous::NFA and dfa::DFA. This is useful for searching an
|
||||||
|
# FST with an Aho-Corasick automaton. Note that this does not apply
|
||||||
|
# to the top-level 'AhoCorasick' type, as it does not implement the
|
||||||
|
# aho_corasick::automaton::Automaton trait, and thus enabling this feature does
|
||||||
|
# not cause it to implement fst::Automaton either.
|
||||||
|
#
|
||||||
|
# NOTE: Currently this feature is not available as `fst` is not at 1.0 yet,
|
||||||
|
# and this would make `fst` a public dependency. If you absolutely need this,
|
||||||
|
# you can copy the (very small) src/transducer.rs file to your tree. It
|
||||||
|
# specifically does not use any private APIs and should work after replacing
|
||||||
|
# 'crate::' with 'aho_corasick::'.
|
||||||
|
#
|
||||||
|
# NOTE: I think my current plan is to flip this around an add an optional
|
||||||
|
# dependency on 'aho-corasick' to the 'fst' crate and move the trait impls
|
||||||
|
# there. But I haven't gotten around to it yet.
|
||||||
|
# transducer = ["fst"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = { version = "0.4.17", optional = true }
|
||||||
|
memchr = { version = "2.4.0", default-features = false, optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
doc-comment = "0.3.3"
|
||||||
|
# fst = "0.4.5"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
# We want to document all features.
|
||||||
|
all-features = true
|
||||||
|
# This opts into a nightly unstable option to show the features that need to be
|
||||||
|
# enabled for public API items. To do that, we set 'docsrs', and when that's
|
||||||
|
# enabled, we enable the 'doc_cfg' feature.
|
||||||
|
#
|
||||||
|
# To test this locally, run:
|
||||||
|
#
|
||||||
|
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
|
||||||
|
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[profile.bench]
|
||||||
|
debug = true
|
||||||
481
third_party/crates/aho-corasick-1.1.5/DESIGN.md
vendored
Normal file
481
third_party/crates/aho-corasick-1.1.5/DESIGN.md
vendored
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
This document describes the internal design of this crate, which is an object
|
||||||
|
lesson in what happens when you take a fairly simple old algorithm like
|
||||||
|
Aho-Corasick and make it fast and production ready.
|
||||||
|
|
||||||
|
The target audience of this document is Rust programmers that have some
|
||||||
|
familiarity with string searching, however, one does not need to know the
|
||||||
|
Aho-Corasick algorithm in order to read this (it is explained below). One
|
||||||
|
should, however, know what a trie is. (If you don't, go read its Wikipedia
|
||||||
|
article.)
|
||||||
|
|
||||||
|
The center-piece of this crate is an implementation of Aho-Corasick. On its
|
||||||
|
own, Aho-Corasick isn't that complicated. The complex pieces come from the
|
||||||
|
different variants of Aho-Corasick implemented in this crate. Specifically,
|
||||||
|
they are:
|
||||||
|
|
||||||
|
* Aho-Corasick as a noncontiguous NFA. States have their transitions
|
||||||
|
represented sparsely, and each state puts its transitions in its own separate
|
||||||
|
allocation. Hence the same "noncontiguous."
|
||||||
|
* Aho-Corasick as a contiguous NFA. This NFA uses a single allocation to
|
||||||
|
represent the transitions of all states. That is, transitions are laid out
|
||||||
|
contiguously in memory. Moreover, states near the starting state are
|
||||||
|
represented densely, such that finding the next state ID takes a constant
|
||||||
|
number of instructions.
|
||||||
|
* Aho-Corasick as a DFA. In this case, all states are represented densely in
|
||||||
|
a transition table that uses one allocation.
|
||||||
|
* Supporting "standard" match semantics, along with its overlapping variant,
|
||||||
|
in addition to leftmost-first and leftmost-longest semantics. The "standard"
|
||||||
|
semantics are typically what you see in a textbook description of
|
||||||
|
Aho-Corasick. However, Aho-Corasick is also useful as an optimization in
|
||||||
|
regex engines, which often use leftmost-first or leftmost-longest semantics.
|
||||||
|
Thus, it is useful to implement those semantics here. The "standard" and
|
||||||
|
"leftmost" search algorithms are subtly different, and also require slightly
|
||||||
|
different construction algorithms.
|
||||||
|
* Support for ASCII case insensitive matching.
|
||||||
|
* Support for accelerating searches when the patterns all start with a small
|
||||||
|
number of fixed bytes. Or alternatively, when the patterns all contain a
|
||||||
|
small number of rare bytes. (Searching for these bytes uses SIMD vectorized
|
||||||
|
code courtesy of `memchr`.)
|
||||||
|
* Transparent support for alternative SIMD vectorized search routines for
|
||||||
|
smaller number of literals, such as the Teddy algorithm. We called these
|
||||||
|
"packed" search routines because they use SIMD. They can often be an order of
|
||||||
|
magnitude faster than just Aho-Corasick, but don't scale as well.
|
||||||
|
* Support for searching streams. This can reuse most of the underlying code,
|
||||||
|
but does require careful buffering support.
|
||||||
|
* Support for anchored searches, which permit efficient "is prefix" checks for
|
||||||
|
a large number of patterns.
|
||||||
|
|
||||||
|
When you combine all of this together along with trying to make everything as
|
||||||
|
fast as possible, what you end up with is enitrely too much code with too much
|
||||||
|
`unsafe`. Alas, I was not smart enough to figure out how to reduce it. Instead,
|
||||||
|
we will explain it.
|
||||||
|
|
||||||
|
|
||||||
|
# Basics
|
||||||
|
|
||||||
|
The fundamental problem this crate is trying to solve is to determine the
|
||||||
|
occurrences of possibly many patterns in a haystack. The naive way to solve
|
||||||
|
this is to look for a match for each pattern at each position in the haystack:
|
||||||
|
|
||||||
|
for i in 0..haystack.len():
|
||||||
|
for p in patterns.iter():
|
||||||
|
if haystack[i..].starts_with(p.bytes()):
|
||||||
|
return Match(p.id(), i, i + p.bytes().len())
|
||||||
|
|
||||||
|
Those four lines are effectively all this crate does. The problem with those
|
||||||
|
four lines is that they are very slow, especially when you're searching for a
|
||||||
|
large number of patterns.
|
||||||
|
|
||||||
|
While there are many different algorithms available to solve this, a popular
|
||||||
|
one is Aho-Corasick. It's a common solution because it's not too hard to
|
||||||
|
implement, scales quite well even when searching for thousands of patterns and
|
||||||
|
is generally pretty fast. Aho-Corasick does well here because, regardless of
|
||||||
|
the number of patterns you're searching for, it always visits each byte in the
|
||||||
|
haystack exactly once. This means, generally speaking, adding more patterns to
|
||||||
|
an Aho-Corasick automaton does not make it slower. (Strictly speaking, however,
|
||||||
|
this is not true, since a larger automaton will make less effective use of the
|
||||||
|
CPU's cache.)
|
||||||
|
|
||||||
|
Aho-Corasick can be succinctly described as a trie with state transitions
|
||||||
|
between some of the nodes that efficiently instruct the search algorithm to
|
||||||
|
try matching alternative keys in the trie. The trick is that these state
|
||||||
|
transitions are arranged such that each byte of input needs to be inspected
|
||||||
|
only once. These state transitions are typically called "failure transitions,"
|
||||||
|
because they instruct the searcher (the thing traversing the automaton while
|
||||||
|
reading from the haystack) what to do when a byte in the haystack does not
|
||||||
|
correspond to a valid transition in the current state of the trie.
|
||||||
|
|
||||||
|
More formally, a failure transition points to a state in the automaton that may
|
||||||
|
lead to a match whose prefix is a proper suffix of the path traversed through
|
||||||
|
the trie so far. (If no such proper suffix exists, then the failure transition
|
||||||
|
points back to the start state of the trie, effectively restarting the search.)
|
||||||
|
This is perhaps simpler to explain pictorally. For example, let's say we built
|
||||||
|
an Aho-Corasick automaton with the following patterns: 'abcd' and 'cef'. The
|
||||||
|
trie looks like this:
|
||||||
|
|
||||||
|
a - S1 - b - S2 - c - S3 - d - S4*
|
||||||
|
/
|
||||||
|
S0 - c - S5 - e - S6 - f - S7*
|
||||||
|
|
||||||
|
where states marked with a `*` are match states (meaning, the search algorithm
|
||||||
|
should stop and report a match to the caller).
|
||||||
|
|
||||||
|
So given this trie, it should be somewhat straight-forward to see how it can
|
||||||
|
be used to determine whether any particular haystack *starts* with either
|
||||||
|
`abcd` or `cef`. It's easy to express this in code:
|
||||||
|
|
||||||
|
fn has_prefix(trie: &Trie, haystack: &[u8]) -> bool {
|
||||||
|
let mut state_id = trie.start();
|
||||||
|
// If the empty pattern is in trie, then state_id is a match state.
|
||||||
|
if trie.is_match(state_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (i, &b) in haystack.iter().enumerate() {
|
||||||
|
state_id = match trie.next_state(state_id, b) {
|
||||||
|
Some(id) => id,
|
||||||
|
// If there was no transition for this state and byte, then we know
|
||||||
|
// the haystack does not start with one of the patterns in our trie.
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
if trie.is_match(state_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
And that's pretty much it. All we do is move through the trie starting with the
|
||||||
|
bytes at the beginning of the haystack. If we find ourselves in a position
|
||||||
|
where we can't move, or if we've looked through the entire haystack without
|
||||||
|
seeing a match state, then we know the haystack does not start with any of the
|
||||||
|
patterns in the trie.
|
||||||
|
|
||||||
|
The meat of the Aho-Corasick algorithm is in how we add failure transitions to
|
||||||
|
our trie to keep searching efficient. Specifically, it permits us to not only
|
||||||
|
check whether a haystack *starts* with any one of a number of patterns, but
|
||||||
|
rather, whether the haystack contains any of a number of patterns *anywhere* in
|
||||||
|
the haystack.
|
||||||
|
|
||||||
|
As mentioned before, failure transitions connect a proper suffix of the path
|
||||||
|
traversed through the trie before, with a path that leads to a match that has a
|
||||||
|
prefix corresponding to that proper suffix. So in our case, for patterns `abcd`
|
||||||
|
and `cef`, with a haystack `abcef`, we want to transition to state `S5` (from
|
||||||
|
the diagram above) from `S3` upon seeing that the byte following `c` is not
|
||||||
|
`d`. Namely, the proper suffix in this example is `c`, which is a prefix of
|
||||||
|
`cef`. So the modified diagram looks like this:
|
||||||
|
|
||||||
|
|
||||||
|
a - S1 - b - S2 - c - S3 - d - S4*
|
||||||
|
/ /
|
||||||
|
/ ----------------
|
||||||
|
/ /
|
||||||
|
S0 - c - S5 - e - S6 - f - S7*
|
||||||
|
|
||||||
|
One thing that isn't shown in this diagram is that *all* states have a failure
|
||||||
|
transition, but only `S3` has a *non-trivial* failure transition. That is, all
|
||||||
|
other states have a failure transition back to the start state. So if our
|
||||||
|
haystack was `abzabcd`, then the searcher would transition back to `S0` after
|
||||||
|
seeing `z`, which effectively restarts the search. (Because there is no pattern
|
||||||
|
in our trie that has a prefix of `bz` or `z`.)
|
||||||
|
|
||||||
|
The code for traversing this *automaton* or *finite state machine* (it is no
|
||||||
|
longer just a trie) is not that much different from the `has_prefix` code
|
||||||
|
above:
|
||||||
|
|
||||||
|
fn contains(fsm: &FiniteStateMachine, haystack: &[u8]) -> bool {
|
||||||
|
let mut state_id = fsm.start();
|
||||||
|
// If the empty pattern is in fsm, then state_id is a match state.
|
||||||
|
if fsm.is_match(state_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (i, &b) in haystack.iter().enumerate() {
|
||||||
|
// While the diagram above doesn't show this, we may wind up needing
|
||||||
|
// to follow multiple failure transitions before we land on a state
|
||||||
|
// in which we can advance. Therefore, when searching for the next
|
||||||
|
// state, we need to loop until we don't see a failure transition.
|
||||||
|
//
|
||||||
|
// This loop terminates because the start state has no empty
|
||||||
|
// transitions. Every transition from the start state either points to
|
||||||
|
// another state, or loops back to the start state.
|
||||||
|
loop {
|
||||||
|
match fsm.next_state(state_id, b) {
|
||||||
|
Some(id) => {
|
||||||
|
state_id = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Unlike our code above, if there was no transition for this
|
||||||
|
// state, then we don't quit. Instead, we look for this state's
|
||||||
|
// failure transition and follow that instead.
|
||||||
|
None => {
|
||||||
|
state_id = fsm.next_fail_state(state_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if fsm.is_match(state_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
Other than the complication around traversing failure transitions, this code
|
||||||
|
is still roughly "traverse the automaton with bytes from the haystack, and quit
|
||||||
|
when a match is seen."
|
||||||
|
|
||||||
|
And that concludes our section on the basics. While we didn't go deep into how
|
||||||
|
the automaton is built (see `src/nfa/noncontiguous.rs`, which has detailed
|
||||||
|
comments about that), the basic structure of Aho-Corasick should be reasonably
|
||||||
|
clear.
|
||||||
|
|
||||||
|
|
||||||
|
# NFAs and DFAs
|
||||||
|
|
||||||
|
There are generally two types of finite automata: non-deterministic finite
|
||||||
|
automata (NFA) and deterministic finite automata (DFA). The difference between
|
||||||
|
them is, principally, that an NFA can be in multiple states at once. This is
|
||||||
|
typically accomplished by things called _epsilon_ transitions, where one could
|
||||||
|
move to a new state without consuming any bytes from the input. (The other
|
||||||
|
mechanism by which NFAs can be in more than one state is where the same byte in
|
||||||
|
a particular state transitions to multiple distinct states.) In contrast, a DFA
|
||||||
|
can only ever be in one state at a time. A DFA has no epsilon transitions, and
|
||||||
|
for any given state, a byte transitions to at most one other state.
|
||||||
|
|
||||||
|
By this formulation, the Aho-Corasick automaton described in the previous
|
||||||
|
section is an NFA. This is because failure transitions are, effectively,
|
||||||
|
epsilon transitions. That is, whenever the automaton is in state `S`, it is
|
||||||
|
actually in the set of states that are reachable by recursively following
|
||||||
|
failure transitions from `S` until you reach the start state. (This means
|
||||||
|
that, for example, the start state is always active since the start state is
|
||||||
|
reachable via failure transitions from any state in the automaton.)
|
||||||
|
|
||||||
|
NFAs have a lot of nice properties. They tend to be easier to construct, and
|
||||||
|
also tend to use less memory. However, their primary downside is that they are
|
||||||
|
typically slower to execute a search with. For example, the code above showing
|
||||||
|
how to search with an Aho-Corasick automaton needs to potentially iterate
|
||||||
|
through many failure transitions for every byte of input. While this is a
|
||||||
|
fairly small amount of overhead, this can add up, especially if the automaton
|
||||||
|
has a lot of overlapping patterns with a lot of failure transitions.
|
||||||
|
|
||||||
|
A DFA's search code, by contrast, looks like this:
|
||||||
|
|
||||||
|
fn contains(dfa: &DFA, haystack: &[u8]) -> bool {
|
||||||
|
let mut state_id = dfa.start();
|
||||||
|
// If the empty pattern is in dfa, then state_id is a match state.
|
||||||
|
if dfa.is_match(state_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (i, &b) in haystack.iter().enumerate() {
|
||||||
|
// An Aho-Corasick DFA *never* has a missing state that requires
|
||||||
|
// failure transitions to be followed. One byte of input advances the
|
||||||
|
// automaton by one state. Always.
|
||||||
|
state_id = dfa.next_state(state_id, b);
|
||||||
|
if dfa.is_match(state_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
The search logic here is much simpler than for the NFA, and this tends to
|
||||||
|
translate into significant performance benefits as well, since there's a lot
|
||||||
|
less work being done for each byte in the haystack. How is this accomplished?
|
||||||
|
It's done by pre-following all failure transitions for all states for all bytes
|
||||||
|
in the alphabet, and then building a single state transition table. Building
|
||||||
|
this DFA can be much more costly than building the NFA, and use much more
|
||||||
|
memory, but the better performance can be worth it.
|
||||||
|
|
||||||
|
Users of this crate can actually choose between using one of two possible NFAs
|
||||||
|
(noncontiguous or contiguous) or a DFA. By default, a contiguous NFA is used,
|
||||||
|
in most circumstances, but if the number of patterns is small enough a DFA will
|
||||||
|
be used. A contiguous NFA is chosen because it uses orders of magnitude less
|
||||||
|
memory than a DFA, takes only a little longer to build than a noncontiguous
|
||||||
|
NFA and usually gets pretty close to the search speed of a DFA. (Callers can
|
||||||
|
override this automatic selection via the `AhoCorasickBuilder::start_kind`
|
||||||
|
configuration.)
|
||||||
|
|
||||||
|
|
||||||
|
# More DFA tricks
|
||||||
|
|
||||||
|
As described in the previous section, one of the downsides of using a DFA
|
||||||
|
is that it uses more memory and can take longer to build. One small way of
|
||||||
|
mitigating these concerns is to map the alphabet used by the automaton into
|
||||||
|
a smaller space. Typically, the alphabet of a DFA has 256 elements in it:
|
||||||
|
one element for each possible value that fits into a byte. However, in many
|
||||||
|
cases, one does not need the full alphabet. For example, if all patterns in an
|
||||||
|
Aho-Corasick automaton are ASCII letters, then this only uses up 52 distinct
|
||||||
|
bytes. As far as the automaton is concerned, the rest of the 204 bytes are
|
||||||
|
indistinguishable from one another: they will never disrciminate between a
|
||||||
|
match or a non-match. Therefore, in cases like that, the alphabet can be shrunk
|
||||||
|
to just 53 elements. One for each ASCII letter, and then another to serve as a
|
||||||
|
placeholder for every other unused byte.
|
||||||
|
|
||||||
|
In practice, this library doesn't quite compute the optimal set of equivalence
|
||||||
|
classes, but it's close enough in most cases. The key idea is that this then
|
||||||
|
allows the transition table for the DFA to be potentially much smaller. The
|
||||||
|
downside of doing this, however, is that since the transition table is defined
|
||||||
|
in terms of this smaller alphabet space, every byte in the haystack must be
|
||||||
|
re-mapped to this smaller space. This requires an additional 256-byte table.
|
||||||
|
In practice, this can lead to a small search time hit, but it can be difficult
|
||||||
|
to measure. Moreover, it can sometimes lead to faster search times for bigger
|
||||||
|
automata, since it could be difference between more parts of the automaton
|
||||||
|
staying in the CPU cache or not.
|
||||||
|
|
||||||
|
One other trick for DFAs employed by this crate is the notion of premultiplying
|
||||||
|
state identifiers. Specifically, the normal way to compute the next transition
|
||||||
|
in a DFA is via the following (assuming that the transition table is laid out
|
||||||
|
sequentially in memory, in row-major order, where the rows are states):
|
||||||
|
|
||||||
|
next_state_id = dfa.transitions[current_state_id * 256 + current_byte]
|
||||||
|
|
||||||
|
However, since the value `256` is a fixed constant, we can actually premultiply
|
||||||
|
the state identifiers in the table when we build the table initially. Then, the
|
||||||
|
next transition computation simply becomes:
|
||||||
|
|
||||||
|
next_state_id = dfa.transitions[current_state_id + current_byte]
|
||||||
|
|
||||||
|
This doesn't seem like much, but when this is being executed for every byte of
|
||||||
|
input that you're searching, saving that extra multiplication instruction can
|
||||||
|
add up.
|
||||||
|
|
||||||
|
The same optimization works even when equivalence classes are enabled, as
|
||||||
|
described above. The only difference is that the premultiplication is by the
|
||||||
|
total number of equivalence classes instead of 256.
|
||||||
|
|
||||||
|
There isn't much downside to premultiplying state identifiers, other than it
|
||||||
|
imposes a smaller limit on the total number of states in the DFA. Namely, with
|
||||||
|
premultiplied state identifiers, you run out of room in your state identifier
|
||||||
|
representation more rapidly than if the identifiers are just state indices.
|
||||||
|
|
||||||
|
Both equivalence classes and premultiplication are always enabled. There is a
|
||||||
|
`AhoCorasickBuilder::byte_classes` configuration, but disabling this just makes
|
||||||
|
it so there are always 256 equivalence classes, i.e., every class corresponds
|
||||||
|
to precisely one byte. When it's disabled, the equivalence class map itself is
|
||||||
|
still used. The purpose of disabling it is when one is debugging the underlying
|
||||||
|
automaton. It can be easier to comprehend when it uses actual byte values for
|
||||||
|
its transitions instead of equivalence classes.
|
||||||
|
|
||||||
|
|
||||||
|
# Match semantics
|
||||||
|
|
||||||
|
One of the more interesting things about this implementation of Aho-Corasick
|
||||||
|
that (as far as this author knows) separates it from other implementations, is
|
||||||
|
that it natively supports leftmost-first and leftmost-longest match semantics.
|
||||||
|
Briefly, match semantics refer to the decision procedure by which searching
|
||||||
|
will disambiguate matches when there are multiple to choose from:
|
||||||
|
|
||||||
|
* **standard** match semantics emits matches as soon as they are detected by
|
||||||
|
the automaton. This is typically equivalent to the textbook non-overlapping
|
||||||
|
formulation of Aho-Corasick.
|
||||||
|
* **leftmost-first** match semantics means that 1) the next match is the match
|
||||||
|
starting at the leftmost position and 2) among multiple matches starting at
|
||||||
|
the same leftmost position, the match corresponding to the pattern provided
|
||||||
|
first by the caller is reported.
|
||||||
|
* **leftmost-longest** is like leftmost-first, except when there are multiple
|
||||||
|
matches starting at the same leftmost position, the pattern corresponding to
|
||||||
|
the longest match is returned.
|
||||||
|
|
||||||
|
(The crate API documentation discusses these differences, with examples, in
|
||||||
|
more depth on the `MatchKind` type.)
|
||||||
|
|
||||||
|
The reason why supporting these match semantics is important is because it
|
||||||
|
gives the user more control over the match procedure. For example,
|
||||||
|
leftmost-first permits users to implement match priority by simply putting the
|
||||||
|
higher priority patterns first. Leftmost-longest, on the other hand, permits
|
||||||
|
finding the longest possible match, which might be useful when trying to find
|
||||||
|
words matching a dictionary. Additionally, regex engines often want to use
|
||||||
|
Aho-Corasick as an optimization when searching for an alternation of literals.
|
||||||
|
In order to preserve correct match semantics, regex engines typically can't use
|
||||||
|
the standard textbook definition directly, since regex engines will implement
|
||||||
|
either leftmost-first (Perl-like) or leftmost-longest (POSIX) match semantics.
|
||||||
|
|
||||||
|
Supporting leftmost semantics requires a couple key changes:
|
||||||
|
|
||||||
|
* Constructing the Aho-Corasick automaton changes a bit in both how the trie is
|
||||||
|
constructed and how failure transitions are found. Namely, only a subset
|
||||||
|
of the failure transitions are added. Specifically, only the failure
|
||||||
|
transitions that either do not occur after a match or do occur after a match
|
||||||
|
but preserve that match are kept. (More details on this can be found in
|
||||||
|
`src/nfa/noncontiguous.rs`.)
|
||||||
|
* The search algorithm changes slightly. Since we are looking for the leftmost
|
||||||
|
match, we cannot quit as soon as a match is detected. Instead, after a match
|
||||||
|
is detected, we must keep searching until either the end of the input or
|
||||||
|
until a dead state is seen. (Dead states are not used for standard match
|
||||||
|
semantics. Dead states mean that searching should stop after a match has been
|
||||||
|
found.)
|
||||||
|
|
||||||
|
Most other implementations of Aho-Corasick do support leftmost match semantics,
|
||||||
|
but they do it with more overhead at search time, or even worse, with a queue
|
||||||
|
of matches and sophisticated hijinks to disambiguate the matches. While our
|
||||||
|
construction algorithm becomes a bit more complicated, the correct match
|
||||||
|
semantics fall out from the structure of the automaton itself.
|
||||||
|
|
||||||
|
|
||||||
|
# Overlapping matches
|
||||||
|
|
||||||
|
One of the nice properties of an Aho-Corasick automaton is that it can report
|
||||||
|
all possible matches, even when they overlap with one another. In this mode,
|
||||||
|
the match semantics don't matter, since all possible matches are reported.
|
||||||
|
Overlapping searches work just like regular searches, except the state
|
||||||
|
identifier at which the previous search left off is carried over to the next
|
||||||
|
search, so that it can pick up where it left off. If there are additional
|
||||||
|
matches at that state, then they are reported before resuming the search.
|
||||||
|
|
||||||
|
Enabling leftmost-first or leftmost-longest match semantics causes the
|
||||||
|
automaton to use a subset of all failure transitions, which means that
|
||||||
|
overlapping searches cannot be used. Therefore, if leftmost match semantics are
|
||||||
|
used, attempting to do an overlapping search will return an error (or panic
|
||||||
|
when using the infallible APIs). Thus, to get overlapping searches, the caller
|
||||||
|
must use the default standard match semantics. This behavior was chosen because
|
||||||
|
there are only two alternatives, which were deemed worse:
|
||||||
|
|
||||||
|
* Compile two automatons internally, one for standard semantics and one for
|
||||||
|
the semantics requested by the caller (if not standard).
|
||||||
|
* Create a new type, distinct from the `AhoCorasick` type, which has different
|
||||||
|
capabilities based on the configuration options.
|
||||||
|
|
||||||
|
The first is untenable because of the amount of memory used by the automaton.
|
||||||
|
The second increases the complexity of the API too much by adding too many
|
||||||
|
types that do similar things. It is conceptually much simpler to keep all
|
||||||
|
searching isolated to a single type.
|
||||||
|
|
||||||
|
|
||||||
|
# Stream searching
|
||||||
|
|
||||||
|
Since Aho-Corasick is an automaton, it is possible to do partial searches on
|
||||||
|
partial parts of the haystack, and then resume that search on subsequent pieces
|
||||||
|
of the haystack. This is useful when the haystack you're trying to search is
|
||||||
|
not stored contiguously in memory, or if one does not want to read the entire
|
||||||
|
haystack into memory at once.
|
||||||
|
|
||||||
|
Currently, only standard semantics are supported for stream searching. This is
|
||||||
|
some of the more complicated code in this crate, and is something I would very
|
||||||
|
much like to improve. In particular, it currently has the restriction that it
|
||||||
|
must buffer at least enough of the haystack in memory in order to fit the
|
||||||
|
longest possible match. The difficulty in getting stream searching right is
|
||||||
|
that the implementation choices (such as the buffer size) often impact what the
|
||||||
|
API looks like and what it's allowed to do.
|
||||||
|
|
||||||
|
|
||||||
|
# Prefilters
|
||||||
|
|
||||||
|
In some cases, Aho-Corasick is not the fastest way to find matches containing
|
||||||
|
multiple patterns. Sometimes, the search can be accelerated using highly
|
||||||
|
optimized SIMD routines. For example, consider searching the following
|
||||||
|
patterns:
|
||||||
|
|
||||||
|
Sherlock
|
||||||
|
Moriarty
|
||||||
|
Watson
|
||||||
|
|
||||||
|
It is plausible that it would be much faster to quickly look for occurrences of
|
||||||
|
the leading bytes, `S`, `M` or `W`, before trying to start searching via the
|
||||||
|
automaton. Indeed, this is exactly what this crate will do.
|
||||||
|
|
||||||
|
When there are more than three distinct starting bytes, then this crate will
|
||||||
|
look for three distinct bytes occurring at any position in the patterns, while
|
||||||
|
preferring bytes that are heuristically determined to be rare over others. For
|
||||||
|
example:
|
||||||
|
|
||||||
|
Abuzz
|
||||||
|
Sanchez
|
||||||
|
Vasquez
|
||||||
|
Topaz
|
||||||
|
Waltz
|
||||||
|
|
||||||
|
Here, we have more than 3 distinct starting bytes, but all of the patterns
|
||||||
|
contain `z`, which is typically a rare byte. In this case, the prefilter will
|
||||||
|
scan for `z`, back up a bit, and then execute the Aho-Corasick automaton.
|
||||||
|
|
||||||
|
If all of that fails, then a packed multiple substring algorithm will be
|
||||||
|
attempted. Currently, the only algorithm available for this is Teddy, but more
|
||||||
|
may be added in the future. Teddy is unlike the above prefilters in that it
|
||||||
|
confirms its own matches, so when Teddy is active, it might not be necessary
|
||||||
|
for Aho-Corasick to run at all. However, the current Teddy implementation
|
||||||
|
only works in `x86_64` when SSSE3 or AVX2 are available or in `aarch64`
|
||||||
|
(using NEON), and moreover, only works _well_ when there are a small number
|
||||||
|
of patterns (say, less than 100). Teddy also requires the haystack to be of a
|
||||||
|
certain length (more than 16-34 bytes). When the haystack is shorter than that,
|
||||||
|
Rabin-Karp is used instead. (See `src/packed/rabinkarp.rs`.)
|
||||||
|
|
||||||
|
There is a more thorough description of Teddy at
|
||||||
|
[`src/packed/teddy/README.md`](src/packed/teddy/README.md).
|
||||||
21
third_party/crates/aho-corasick-1.1.5/LICENSE-MIT
vendored
Normal file
21
third_party/crates/aho-corasick-1.1.5/LICENSE-MIT
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Andrew Gallant
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
174
third_party/crates/aho-corasick-1.1.5/README.md
vendored
Normal file
174
third_party/crates/aho-corasick-1.1.5/README.md
vendored
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
aho-corasick
|
||||||
|
============
|
||||||
|
A library for finding occurrences of many patterns at once with SIMD
|
||||||
|
acceleration in some cases. This library provides multiple pattern
|
||||||
|
search principally through an implementation of the
|
||||||
|
[Aho-Corasick algorithm](https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm),
|
||||||
|
which builds a finite state machine for executing searches in linear time.
|
||||||
|
Features include case insensitive matching, overlapping matches, fast searching
|
||||||
|
via SIMD and optional full DFA construction and search & replace in streams.
|
||||||
|
|
||||||
|
[](https://github.com/BurntSushi/aho-corasick/actions)
|
||||||
|
[](https://crates.io/crates/aho-corasick)
|
||||||
|
|
||||||
|
Dual-licensed under MIT or the [UNLICENSE](https://unlicense.org/).
|
||||||
|
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
https://docs.rs/aho-corasick
|
||||||
|
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Run `cargo add aho-corasick` to automatically add this crate as a dependency
|
||||||
|
in your `Cargo.toml` file.
|
||||||
|
|
||||||
|
|
||||||
|
### Example: basic searching
|
||||||
|
|
||||||
|
This example shows how to search for occurrences of multiple patterns
|
||||||
|
simultaneously. Each match includes the pattern that matched along with the
|
||||||
|
byte offsets of the match.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use aho_corasick::{AhoCorasick, PatternID};
|
||||||
|
|
||||||
|
let patterns = &["apple", "maple", "Snapple"];
|
||||||
|
let haystack = "Nobody likes maple in their apple flavored Snapple.";
|
||||||
|
|
||||||
|
let ac = AhoCorasick::new(patterns).unwrap();
|
||||||
|
let mut matches = vec![];
|
||||||
|
for mat in ac.find_iter(haystack) {
|
||||||
|
matches.push((mat.pattern(), mat.start(), mat.end()));
|
||||||
|
}
|
||||||
|
assert_eq!(matches, vec![
|
||||||
|
(PatternID::must(1), 13, 18),
|
||||||
|
(PatternID::must(0), 28, 33),
|
||||||
|
(PatternID::must(2), 43, 50),
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Example: ASCII case insensitivity
|
||||||
|
|
||||||
|
This is like the previous example, but matches `Snapple` case insensitively
|
||||||
|
using `AhoCorasickBuilder`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use aho_corasick::{AhoCorasick, PatternID};
|
||||||
|
|
||||||
|
let patterns = &["apple", "maple", "snapple"];
|
||||||
|
let haystack = "Nobody likes maple in their apple flavored Snapple.";
|
||||||
|
|
||||||
|
let ac = AhoCorasick::builder()
|
||||||
|
.ascii_case_insensitive(true)
|
||||||
|
.build(patterns)
|
||||||
|
.unwrap();
|
||||||
|
let mut matches = vec![];
|
||||||
|
for mat in ac.find_iter(haystack) {
|
||||||
|
matches.push((mat.pattern(), mat.start(), mat.end()));
|
||||||
|
}
|
||||||
|
assert_eq!(matches, vec![
|
||||||
|
(PatternID::must(1), 13, 18),
|
||||||
|
(PatternID::must(0), 28, 33),
|
||||||
|
(PatternID::must(2), 43, 50),
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Example: replacing matches in a stream
|
||||||
|
|
||||||
|
This example shows how to execute a search and replace on a stream without
|
||||||
|
loading the entire stream into memory first.
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
use aho_corasick::AhoCorasick;
|
||||||
|
|
||||||
|
let patterns = &["fox", "brown", "quick"];
|
||||||
|
let replace_with = &["sloth", "grey", "slow"];
|
||||||
|
|
||||||
|
// In a real example, these might be `std::fs::File`s instead. All you need to
|
||||||
|
// do is supply a pair of `std::io::Read` and `std::io::Write` implementations.
|
||||||
|
let rdr = "The quick brown fox.";
|
||||||
|
let mut wtr = vec![];
|
||||||
|
|
||||||
|
let ac = AhoCorasick::new(patterns).unwrap();
|
||||||
|
ac.stream_replace_all(rdr.as_bytes(), &mut wtr, replace_with)
|
||||||
|
.expect("stream_replace_all failed");
|
||||||
|
assert_eq!(b"The slow grey sloth.".to_vec(), wtr);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Example: finding the leftmost first match
|
||||||
|
|
||||||
|
In the textbook description of Aho-Corasick, its formulation is typically
|
||||||
|
structured such that it reports all possible matches, even when they overlap
|
||||||
|
with another. In many cases, overlapping matches may not be desired, such as
|
||||||
|
the case of finding all successive non-overlapping matches like you might with
|
||||||
|
a standard regular expression.
|
||||||
|
|
||||||
|
Unfortunately the "obvious" way to modify the Aho-Corasick algorithm to do
|
||||||
|
this doesn't always work in the expected way, since it will report matches as
|
||||||
|
soon as they are seen. For example, consider matching the regex `Samwise|Sam`
|
||||||
|
against the text `Samwise`. Most regex engines (that are Perl-like, or
|
||||||
|
non-POSIX) will report `Samwise` as a match, but the standard Aho-Corasick
|
||||||
|
algorithm modified for reporting non-overlapping matches will report `Sam`.
|
||||||
|
|
||||||
|
A novel contribution of this library is the ability to change the match
|
||||||
|
semantics of Aho-Corasick (without additional search time overhead) such that
|
||||||
|
`Samwise` is reported instead. For example, here's the standard approach:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use aho_corasick::AhoCorasick;
|
||||||
|
|
||||||
|
let patterns = &["Samwise", "Sam"];
|
||||||
|
let haystack = "Samwise";
|
||||||
|
|
||||||
|
let ac = AhoCorasick::new(patterns).unwrap();
|
||||||
|
let mat = ac.find(haystack).expect("should have a match");
|
||||||
|
assert_eq!("Sam", &haystack[mat.start()..mat.end()]);
|
||||||
|
```
|
||||||
|
|
||||||
|
And now here's the leftmost-first version, which matches how a Perl-like
|
||||||
|
regex will work:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use aho_corasick::{AhoCorasick, MatchKind};
|
||||||
|
|
||||||
|
let patterns = &["Samwise", "Sam"];
|
||||||
|
let haystack = "Samwise";
|
||||||
|
|
||||||
|
let ac = AhoCorasick::builder()
|
||||||
|
.match_kind(MatchKind::LeftmostFirst)
|
||||||
|
.build(patterns)
|
||||||
|
.unwrap();
|
||||||
|
let mat = ac.find(haystack).expect("should have a match");
|
||||||
|
assert_eq!("Samwise", &haystack[mat.start()..mat.end()]);
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition to leftmost-first semantics, this library also supports
|
||||||
|
leftmost-longest semantics, which match the POSIX behavior of a regular
|
||||||
|
expression alternation. See `MatchKind` in the docs for more details.
|
||||||
|
|
||||||
|
|
||||||
|
### Minimum Rust version policy
|
||||||
|
|
||||||
|
This crate's minimum supported `rustc` version is `1.60.0`.
|
||||||
|
|
||||||
|
The current policy is that the minimum Rust version required to use this crate
|
||||||
|
can be increased in minor version updates. For example, if `crate 1.0` requires
|
||||||
|
Rust 1.20.0, then `crate 1.0.z` for all values of `z` will also require Rust
|
||||||
|
1.20.0 or newer. However, `crate 1.y` for `y > 0` may require a newer minimum
|
||||||
|
version of Rust.
|
||||||
|
|
||||||
|
In general, this crate will be conservative with respect to the minimum
|
||||||
|
supported version of Rust.
|
||||||
|
|
||||||
|
|
||||||
|
### FFI bindings
|
||||||
|
|
||||||
|
* [G-Research/ahocorasick_rs](https://github.com/G-Research/ahocorasick_rs/)
|
||||||
|
is a Python wrapper for this library.
|
||||||
|
* [tmikus/ahocorasick_rs](https://github.com/tmikus/ahocorasick_rs) is a Go
|
||||||
|
wrapper for this library.
|
||||||
24
third_party/crates/aho-corasick-1.1.5/UNLICENSE
vendored
Normal file
24
third_party/crates/aho-corasick-1.1.5/UNLICENSE
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org/>
|
||||||
2
third_party/crates/aho-corasick-1.1.5/rustfmt.toml
vendored
Normal file
2
third_party/crates/aho-corasick-1.1.5/rustfmt.toml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
max_width = 79
|
||||||
|
use_small_heuristics = "max"
|
||||||
2789
third_party/crates/aho-corasick-1.1.5/src/ahocorasick.rs
vendored
Normal file
2789
third_party/crates/aho-corasick-1.1.5/src/ahocorasick.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1608
third_party/crates/aho-corasick-1.1.5/src/automaton.rs
vendored
Normal file
1608
third_party/crates/aho-corasick-1.1.5/src/automaton.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
835
third_party/crates/aho-corasick-1.1.5/src/dfa.rs
vendored
Normal file
835
third_party/crates/aho-corasick-1.1.5/src/dfa.rs
vendored
Normal file
@@ -0,0 +1,835 @@
|
|||||||
|
/*!
|
||||||
|
Provides direct access to a DFA implementation of Aho-Corasick.
|
||||||
|
|
||||||
|
This is a low-level API that generally only needs to be used in niche
|
||||||
|
circumstances. When possible, prefer using [`AhoCorasick`](crate::AhoCorasick)
|
||||||
|
instead of a DFA directly. Using an `DFA` directly is typically only necessary
|
||||||
|
when one needs access to the [`Automaton`] trait implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use alloc::{vec, vec::Vec};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
automaton::Automaton,
|
||||||
|
nfa::noncontiguous,
|
||||||
|
util::{
|
||||||
|
alphabet::ByteClasses,
|
||||||
|
error::{BuildError, MatchError},
|
||||||
|
int::{Usize, U32},
|
||||||
|
prefilter::Prefilter,
|
||||||
|
primitives::{IteratorIndexExt, PatternID, SmallIndex, StateID},
|
||||||
|
search::{Anchored, MatchKind, StartKind},
|
||||||
|
special::Special,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A DFA implementation of Aho-Corasick.
|
||||||
|
///
|
||||||
|
/// When possible, prefer using [`AhoCorasick`](crate::AhoCorasick) instead of
|
||||||
|
/// this type directly. Using a `DFA` directly is typically only necessary when
|
||||||
|
/// one needs access to the [`Automaton`] trait implementation.
|
||||||
|
///
|
||||||
|
/// This DFA can only be built by first constructing a [`noncontiguous::NFA`].
|
||||||
|
/// Both [`DFA::new`] and [`Builder::build`] do this for you automatically, but
|
||||||
|
/// [`Builder::build_from_noncontiguous`] permits doing it explicitly.
|
||||||
|
///
|
||||||
|
/// A DFA provides the best possible search performance (in this crate) via two
|
||||||
|
/// mechanisms:
|
||||||
|
///
|
||||||
|
/// * All states use a dense representation for their transitions.
|
||||||
|
/// * All failure transitions are pre-computed such that they are never
|
||||||
|
/// explicitly handled at search time.
|
||||||
|
///
|
||||||
|
/// These two facts combined mean that every state transition is performed
|
||||||
|
/// using a constant number of instructions. However, this comes at
|
||||||
|
/// great cost. The memory usage of a DFA can be quite exorbitant.
|
||||||
|
/// It is potentially multiple orders of magnitude greater than a
|
||||||
|
/// [`contiguous::NFA`](crate::nfa::contiguous::NFA) for example. In exchange,
|
||||||
|
/// a DFA will typically have better search speed than a `contiguous::NFA`, but
|
||||||
|
/// not by orders of magnitude.
|
||||||
|
///
|
||||||
|
/// Unless you have a small number of patterns or memory usage is not a concern
|
||||||
|
/// and search performance is critical, a DFA is usually not the best choice.
|
||||||
|
///
|
||||||
|
/// Moreover, unlike the NFAs in this crate, it is costly for a DFA to
|
||||||
|
/// support for anchored and unanchored search configurations. Namely,
|
||||||
|
/// since failure transitions are pre-computed, supporting both anchored
|
||||||
|
/// and unanchored searches requires a duplication of the transition table,
|
||||||
|
/// making the memory usage of such a DFA ever bigger. (The NFAs in this crate
|
||||||
|
/// unconditionally support both anchored and unanchored searches because there
|
||||||
|
/// is essentially no added cost for doing so.) It is for this reason that
|
||||||
|
/// a DFA's support for anchored and unanchored searches can be configured
|
||||||
|
/// via [`Builder::start_kind`]. By default, a DFA only supports unanchored
|
||||||
|
/// searches.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This example shows how to build an `DFA` directly and use it to execute
|
||||||
|
/// [`Automaton::try_find`]:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{
|
||||||
|
/// automaton::Automaton,
|
||||||
|
/// dfa::DFA,
|
||||||
|
/// Input, Match,
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let patterns = &["b", "abc", "abcd"];
|
||||||
|
/// let haystack = "abcd";
|
||||||
|
///
|
||||||
|
/// let nfa = DFA::new(patterns).unwrap();
|
||||||
|
/// assert_eq!(
|
||||||
|
/// Some(Match::must(0, 1..2)),
|
||||||
|
/// nfa.try_find(&Input::new(haystack))?,
|
||||||
|
/// );
|
||||||
|
/// # Ok::<(), Box<dyn std::error::Error>>(())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// It is also possible to implement your own version of `try_find`. See the
|
||||||
|
/// [`Automaton`] documentation for an example.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DFA {
|
||||||
|
/// The DFA transition table. IDs in this table are pre-multiplied. So
|
||||||
|
/// instead of the IDs being 0, 1, 2, 3, ..., they are 0*stride, 1*stride,
|
||||||
|
/// 2*stride, 3*stride, ...
|
||||||
|
trans: Vec<StateID>,
|
||||||
|
/// The matches for every match state in this DFA. This is first indexed by
|
||||||
|
/// state index (so that's `sid >> stride2`) and then by order in which the
|
||||||
|
/// matches are meant to occur.
|
||||||
|
matches: Vec<Vec<PatternID>>,
|
||||||
|
/// The amount of heap memory used, in bytes, by the inner Vecs of
|
||||||
|
/// 'matches'.
|
||||||
|
matches_memory_usage: usize,
|
||||||
|
/// The length of each pattern. This is used to compute the start offset
|
||||||
|
/// of a match.
|
||||||
|
pattern_lens: Vec<SmallIndex>,
|
||||||
|
/// A prefilter for accelerating searches, if one exists.
|
||||||
|
prefilter: Option<Prefilter>,
|
||||||
|
/// The match semantics built into this DFA.
|
||||||
|
match_kind: MatchKind,
|
||||||
|
/// The total number of states in this DFA.
|
||||||
|
state_len: usize,
|
||||||
|
/// The alphabet size, or total number of equivalence classes, for this
|
||||||
|
/// DFA. Note that the actual number of transitions in each state is
|
||||||
|
/// stride=2^stride2, where stride is the smallest power of 2 greater than
|
||||||
|
/// or equal to alphabet_len. We do things this way so that we can use
|
||||||
|
/// bitshifting to go from a state ID to an index into 'matches'.
|
||||||
|
alphabet_len: usize,
|
||||||
|
/// The exponent with a base 2, such that stride=2^stride2. Given a state
|
||||||
|
/// index 'i', its state identifier is 'i << stride2'. Given a state
|
||||||
|
/// identifier 'sid', its state index is 'sid >> stride2'.
|
||||||
|
stride2: usize,
|
||||||
|
/// The equivalence classes for this DFA. All transitions are defined on
|
||||||
|
/// equivalence classes and not on the 256 distinct byte values.
|
||||||
|
byte_classes: ByteClasses,
|
||||||
|
/// The length of the shortest pattern in this automaton.
|
||||||
|
min_pattern_len: usize,
|
||||||
|
/// The length of the longest pattern in this automaton.
|
||||||
|
max_pattern_len: usize,
|
||||||
|
/// The information required to deduce which states are "special" in this
|
||||||
|
/// DFA.
|
||||||
|
special: Special,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DFA {
|
||||||
|
/// Create a new Aho-Corasick DFA using the default configuration.
|
||||||
|
///
|
||||||
|
/// Use a [`Builder`] if you want to change the configuration.
|
||||||
|
pub fn new<I, P>(patterns: I) -> Result<DFA, BuildError>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = P>,
|
||||||
|
P: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
DFA::builder().build(patterns)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A convenience method for returning a new Aho-Corasick DFA builder.
|
||||||
|
///
|
||||||
|
/// This usually permits one to just import the `DFA` type.
|
||||||
|
pub fn builder() -> Builder {
|
||||||
|
Builder::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DFA {
|
||||||
|
/// A sentinel state ID indicating that a search should stop once it has
|
||||||
|
/// entered this state. When a search stops, it returns a match if one has
|
||||||
|
/// been found, otherwise no match. A DFA always has an actual dead state
|
||||||
|
/// at this ID.
|
||||||
|
///
|
||||||
|
/// N.B. DFAs, unlike NFAs, do not have any notion of a FAIL state.
|
||||||
|
/// Namely, the whole point of a DFA is that the FAIL state is completely
|
||||||
|
/// compiled away. That is, DFA construction involves pre-computing the
|
||||||
|
/// failure transitions everywhere, such that failure transitions are no
|
||||||
|
/// longer used at search time. This, combined with its uniformly dense
|
||||||
|
/// representation, are the two most important factors in why it's faster
|
||||||
|
/// than the NFAs in this crate.
|
||||||
|
const DEAD: StateID = StateID::new_unchecked(0);
|
||||||
|
|
||||||
|
/// Adds the given pattern IDs as matches to the given state and also
|
||||||
|
/// records the added memory usage.
|
||||||
|
fn set_matches(
|
||||||
|
&mut self,
|
||||||
|
sid: StateID,
|
||||||
|
pids: impl Iterator<Item = PatternID>,
|
||||||
|
) {
|
||||||
|
let index = (sid.as_usize() >> self.stride2).checked_sub(2).unwrap();
|
||||||
|
let mut at_least_one = false;
|
||||||
|
for pid in pids {
|
||||||
|
self.matches[index].push(pid);
|
||||||
|
self.matches_memory_usage += PatternID::SIZE;
|
||||||
|
at_least_one = true;
|
||||||
|
}
|
||||||
|
assert!(at_least_one, "match state must have non-empty pids");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: 'start_state' always returns a valid state ID, 'next_state' always
|
||||||
|
// returns a valid state ID given a valid state ID. We otherwise claim that
|
||||||
|
// all other methods are correct as well.
|
||||||
|
unsafe impl Automaton for DFA {
|
||||||
|
#[inline(always)]
|
||||||
|
fn start_state(&self, anchored: Anchored) -> Result<StateID, MatchError> {
|
||||||
|
// Either of the start state IDs can be DEAD, in which case, support
|
||||||
|
// for that type of search is not provided by this DFA. Which start
|
||||||
|
// state IDs are inactive depends on the 'StartKind' configuration at
|
||||||
|
// DFA construction time.
|
||||||
|
match anchored {
|
||||||
|
Anchored::No => {
|
||||||
|
let start = self.special.start_unanchored_id;
|
||||||
|
if start == DFA::DEAD {
|
||||||
|
Err(MatchError::invalid_input_unanchored())
|
||||||
|
} else {
|
||||||
|
Ok(start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Anchored::Yes => {
|
||||||
|
let start = self.special.start_anchored_id;
|
||||||
|
if start == DFA::DEAD {
|
||||||
|
Err(MatchError::invalid_input_anchored())
|
||||||
|
} else {
|
||||||
|
Ok(start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn next_state(
|
||||||
|
&self,
|
||||||
|
_anchored: Anchored,
|
||||||
|
sid: StateID,
|
||||||
|
byte: u8,
|
||||||
|
) -> StateID {
|
||||||
|
let class = self.byte_classes.get(byte);
|
||||||
|
self.trans[(sid.as_u32() + u32::from(class)).as_usize()]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn is_special(&self, sid: StateID) -> bool {
|
||||||
|
sid <= self.special.max_special_id
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn is_dead(&self, sid: StateID) -> bool {
|
||||||
|
sid == DFA::DEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn is_match(&self, sid: StateID) -> bool {
|
||||||
|
!self.is_dead(sid) && sid <= self.special.max_match_id
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn is_start(&self, sid: StateID) -> bool {
|
||||||
|
sid == self.special.start_unanchored_id
|
||||||
|
|| sid == self.special.start_anchored_id
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn match_kind(&self) -> MatchKind {
|
||||||
|
self.match_kind
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn patterns_len(&self) -> usize {
|
||||||
|
self.pattern_lens.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn pattern_len(&self, pid: PatternID) -> usize {
|
||||||
|
self.pattern_lens[pid].as_usize()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn min_pattern_len(&self) -> usize {
|
||||||
|
self.min_pattern_len
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn max_pattern_len(&self) -> usize {
|
||||||
|
self.max_pattern_len
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn match_len(&self, sid: StateID) -> usize {
|
||||||
|
debug_assert!(self.is_match(sid));
|
||||||
|
let offset = (sid.as_usize() >> self.stride2) - 2;
|
||||||
|
self.matches[offset].len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn match_pattern(&self, sid: StateID, index: usize) -> PatternID {
|
||||||
|
debug_assert!(self.is_match(sid));
|
||||||
|
let offset = (sid.as_usize() >> self.stride2) - 2;
|
||||||
|
self.matches[offset][index]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn memory_usage(&self) -> usize {
|
||||||
|
use core::mem::size_of;
|
||||||
|
|
||||||
|
(self.trans.len() * size_of::<u32>())
|
||||||
|
+ (self.matches.len() * size_of::<Vec<PatternID>>())
|
||||||
|
+ self.matches_memory_usage
|
||||||
|
+ (self.pattern_lens.len() * size_of::<SmallIndex>())
|
||||||
|
+ self.prefilter.as_ref().map_or(0, |p| p.memory_usage())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn prefilter(&self) -> Option<&Prefilter> {
|
||||||
|
self.prefilter.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for DFA {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
use crate::{
|
||||||
|
automaton::{fmt_state_indicator, sparse_transitions},
|
||||||
|
util::debug::DebugByte,
|
||||||
|
};
|
||||||
|
|
||||||
|
writeln!(f, "dfa::DFA(")?;
|
||||||
|
for index in 0..self.state_len {
|
||||||
|
let sid = StateID::new_unchecked(index << self.stride2);
|
||||||
|
// While we do currently include the FAIL state in the transition
|
||||||
|
// table (to simplify construction), it is never actually used. It
|
||||||
|
// poses problems with the code below because it gets treated as
|
||||||
|
// a match state incidentally when it is, of course, not. So we
|
||||||
|
// special case it. The fail state is always the first state after
|
||||||
|
// the dead state.
|
||||||
|
//
|
||||||
|
// If the construction is changed to remove the fail state (it
|
||||||
|
// probably should be), then this special case should be updated.
|
||||||
|
if index == 1 {
|
||||||
|
writeln!(f, "F {:06}:", sid.as_usize())?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fmt_state_indicator(f, self, sid)?;
|
||||||
|
write!(f, "{:06}: ", sid.as_usize())?;
|
||||||
|
|
||||||
|
let it = (0..self.byte_classes.alphabet_len()).map(|class| {
|
||||||
|
(class.as_u8(), self.trans[sid.as_usize() + class])
|
||||||
|
});
|
||||||
|
for (i, (start, end, next)) in sparse_transitions(it).enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
if start == end {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{:?} => {:?}",
|
||||||
|
DebugByte(start),
|
||||||
|
next.as_usize()
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{:?}-{:?} => {:?}",
|
||||||
|
DebugByte(start),
|
||||||
|
DebugByte(end),
|
||||||
|
next.as_usize()
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "\n")?;
|
||||||
|
if self.is_match(sid) {
|
||||||
|
write!(f, " matches: ")?;
|
||||||
|
for i in 0..self.match_len(sid) {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
let pid = self.match_pattern(sid, i);
|
||||||
|
write!(f, "{}", pid.as_usize())?;
|
||||||
|
}
|
||||||
|
write!(f, "\n")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(f, "match kind: {:?}", self.match_kind)?;
|
||||||
|
writeln!(f, "prefilter: {:?}", self.prefilter.is_some())?;
|
||||||
|
writeln!(f, "state length: {:?}", self.state_len)?;
|
||||||
|
writeln!(f, "pattern length: {:?}", self.patterns_len())?;
|
||||||
|
writeln!(f, "shortest pattern length: {:?}", self.min_pattern_len)?;
|
||||||
|
writeln!(f, "longest pattern length: {:?}", self.max_pattern_len)?;
|
||||||
|
writeln!(f, "alphabet length: {:?}", self.alphabet_len)?;
|
||||||
|
writeln!(f, "stride: {:?}", 1 << self.stride2)?;
|
||||||
|
writeln!(f, "byte classes: {:?}", self.byte_classes)?;
|
||||||
|
writeln!(f, "memory usage: {:?}", self.memory_usage())?;
|
||||||
|
writeln!(f, ")")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A builder for configuring an Aho-Corasick DFA.
|
||||||
|
///
|
||||||
|
/// This builder has a subset of the options available to a
|
||||||
|
/// [`AhoCorasickBuilder`](crate::AhoCorasickBuilder). Of the shared options,
|
||||||
|
/// their behavior is identical.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Builder {
|
||||||
|
noncontiguous: noncontiguous::Builder,
|
||||||
|
start_kind: StartKind,
|
||||||
|
byte_classes: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Builder {
|
||||||
|
fn default() -> Builder {
|
||||||
|
Builder {
|
||||||
|
noncontiguous: noncontiguous::Builder::new(),
|
||||||
|
start_kind: StartKind::Unanchored,
|
||||||
|
byte_classes: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
/// Create a new builder for configuring an Aho-Corasick DFA.
|
||||||
|
pub fn new() -> Builder {
|
||||||
|
Builder::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build an Aho-Corasick DFA from the given iterator of patterns.
|
||||||
|
///
|
||||||
|
/// A builder may be reused to create more DFAs.
|
||||||
|
pub fn build<I, P>(&self, patterns: I) -> Result<DFA, BuildError>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = P>,
|
||||||
|
P: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
let nnfa = self.noncontiguous.build(patterns)?;
|
||||||
|
self.build_from_noncontiguous(&nnfa)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build an Aho-Corasick DFA from the given noncontiguous NFA.
|
||||||
|
///
|
||||||
|
/// Note that when this method is used, only the `start_kind` and
|
||||||
|
/// `byte_classes` settings on this builder are respected. The other
|
||||||
|
/// settings only apply to the initial construction of the Aho-Corasick
|
||||||
|
/// automaton. Since using this method requires that initial construction
|
||||||
|
/// has already completed, all settings impacting only initial construction
|
||||||
|
/// are no longer relevant.
|
||||||
|
pub fn build_from_noncontiguous(
|
||||||
|
&self,
|
||||||
|
nnfa: &noncontiguous::NFA,
|
||||||
|
) -> Result<DFA, BuildError> {
|
||||||
|
debug!("building DFA");
|
||||||
|
let byte_classes = if self.byte_classes {
|
||||||
|
nnfa.byte_classes().clone()
|
||||||
|
} else {
|
||||||
|
ByteClasses::singletons()
|
||||||
|
};
|
||||||
|
let state_len = match self.start_kind {
|
||||||
|
StartKind::Unanchored | StartKind::Anchored => nnfa.states().len(),
|
||||||
|
StartKind::Both => {
|
||||||
|
// These unwraps are OK because we know that the number of
|
||||||
|
// NFA states is < StateID::LIMIT which is in turn less than
|
||||||
|
// i32::MAX. Thus, there is always room to multiply by 2.
|
||||||
|
// Finally, the number of states is always at least 4 in the
|
||||||
|
// NFA (DEAD, FAIL, START-UNANCHORED, START-ANCHORED), so the
|
||||||
|
// subtraction of 4 is okay.
|
||||||
|
//
|
||||||
|
// Note that we subtract 4 because the "anchored" part of
|
||||||
|
// the DFA duplicates the unanchored part (without failure
|
||||||
|
// transitions), but reuses the DEAD, FAIL and START states.
|
||||||
|
nnfa.states()
|
||||||
|
.len()
|
||||||
|
.checked_mul(2)
|
||||||
|
.unwrap()
|
||||||
|
.checked_sub(4)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let trans_len =
|
||||||
|
match state_len.checked_shl(byte_classes.stride2().as_u32()) {
|
||||||
|
Some(trans_len) => trans_len,
|
||||||
|
None => {
|
||||||
|
return Err(BuildError::state_id_overflow(
|
||||||
|
StateID::MAX.as_u64(),
|
||||||
|
usize::MAX.as_u64(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
StateID::new(trans_len.checked_sub(byte_classes.stride()).unwrap())
|
||||||
|
.map_err(|e| {
|
||||||
|
BuildError::state_id_overflow(
|
||||||
|
StateID::MAX.as_u64(),
|
||||||
|
e.attempted(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let num_match_states = match self.start_kind {
|
||||||
|
StartKind::Unanchored | StartKind::Anchored => {
|
||||||
|
nnfa.special().max_match_id.as_usize().checked_sub(1).unwrap()
|
||||||
|
}
|
||||||
|
StartKind::Both => nnfa
|
||||||
|
.special()
|
||||||
|
.max_match_id
|
||||||
|
.as_usize()
|
||||||
|
.checked_sub(1)
|
||||||
|
.unwrap()
|
||||||
|
.checked_mul(2)
|
||||||
|
.unwrap(),
|
||||||
|
};
|
||||||
|
let mut dfa = DFA {
|
||||||
|
trans: vec![DFA::DEAD; trans_len],
|
||||||
|
matches: vec![vec![]; num_match_states],
|
||||||
|
matches_memory_usage: 0,
|
||||||
|
pattern_lens: nnfa.pattern_lens_raw().to_vec(),
|
||||||
|
prefilter: nnfa.prefilter().cloned(),
|
||||||
|
match_kind: nnfa.match_kind(),
|
||||||
|
state_len,
|
||||||
|
alphabet_len: byte_classes.alphabet_len(),
|
||||||
|
stride2: byte_classes.stride2(),
|
||||||
|
byte_classes,
|
||||||
|
min_pattern_len: nnfa.min_pattern_len(),
|
||||||
|
max_pattern_len: nnfa.max_pattern_len(),
|
||||||
|
// The special state IDs are set later.
|
||||||
|
special: Special::zero(),
|
||||||
|
};
|
||||||
|
match self.start_kind {
|
||||||
|
StartKind::Both => {
|
||||||
|
self.finish_build_both_starts(nnfa, &mut dfa);
|
||||||
|
}
|
||||||
|
StartKind::Unanchored => {
|
||||||
|
self.finish_build_one_start(Anchored::No, nnfa, &mut dfa);
|
||||||
|
}
|
||||||
|
StartKind::Anchored => {
|
||||||
|
self.finish_build_one_start(Anchored::Yes, nnfa, &mut dfa)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!(
|
||||||
|
"DFA built, <states: {:?}, size: {:?}, \
|
||||||
|
alphabet len: {:?}, stride: {:?}>",
|
||||||
|
dfa.state_len,
|
||||||
|
dfa.memory_usage(),
|
||||||
|
dfa.byte_classes.alphabet_len(),
|
||||||
|
dfa.byte_classes.stride(),
|
||||||
|
);
|
||||||
|
// The vectors can grow ~twice as big during construction because a
|
||||||
|
// Vec amortizes growth. But here, let's shrink things back down to
|
||||||
|
// what we actually need since we're never going to add more to it.
|
||||||
|
dfa.trans.shrink_to_fit();
|
||||||
|
dfa.pattern_lens.shrink_to_fit();
|
||||||
|
dfa.matches.shrink_to_fit();
|
||||||
|
// TODO: We might also want to shrink each Vec inside of `dfa.matches`,
|
||||||
|
// or even better, convert it to one contiguous allocation. But I think
|
||||||
|
// I went with nested allocs for good reason (can't remember), so this
|
||||||
|
// may be tricky to do. I decided not to shrink them here because it
|
||||||
|
// might require a fair bit of work to do. It's unclear whether it's
|
||||||
|
// worth it.
|
||||||
|
Ok(dfa)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finishes building a DFA for either unanchored or anchored searches,
|
||||||
|
/// but NOT both.
|
||||||
|
fn finish_build_one_start(
|
||||||
|
&self,
|
||||||
|
anchored: Anchored,
|
||||||
|
nnfa: &noncontiguous::NFA,
|
||||||
|
dfa: &mut DFA,
|
||||||
|
) {
|
||||||
|
// This function always succeeds because we check above that all of the
|
||||||
|
// states in the NFA can be mapped to DFA state IDs.
|
||||||
|
let stride2 = dfa.stride2;
|
||||||
|
let old2new = |oldsid: StateID| {
|
||||||
|
StateID::new_unchecked(oldsid.as_usize() << stride2)
|
||||||
|
};
|
||||||
|
for (oldsid, state) in nnfa.states().iter().with_state_ids() {
|
||||||
|
let newsid = old2new(oldsid);
|
||||||
|
if state.is_match() {
|
||||||
|
dfa.set_matches(newsid, nnfa.iter_matches(oldsid));
|
||||||
|
}
|
||||||
|
sparse_iter(
|
||||||
|
nnfa,
|
||||||
|
oldsid,
|
||||||
|
&dfa.byte_classes,
|
||||||
|
|byte, class, mut oldnextsid| {
|
||||||
|
if oldnextsid == noncontiguous::NFA::FAIL {
|
||||||
|
if anchored.is_anchored() {
|
||||||
|
oldnextsid = noncontiguous::NFA::DEAD;
|
||||||
|
} else if state.fail() == noncontiguous::NFA::DEAD {
|
||||||
|
// This is a special case that avoids following
|
||||||
|
// DEAD transitions in a non-contiguous NFA.
|
||||||
|
// Following these transitions is pretty slow
|
||||||
|
// because the non-contiguous NFA will always use
|
||||||
|
// a sparse representation for it (because the
|
||||||
|
// DEAD state is usually treated as a sentinel).
|
||||||
|
// The *vast* majority of failure states are DEAD
|
||||||
|
// states, so this winds up being pretty slow if
|
||||||
|
// we go through the non-contiguous NFA state
|
||||||
|
// transition logic. Instead, just do it ourselves.
|
||||||
|
oldnextsid = noncontiguous::NFA::DEAD;
|
||||||
|
} else {
|
||||||
|
oldnextsid = nnfa.next_state(
|
||||||
|
Anchored::No,
|
||||||
|
state.fail(),
|
||||||
|
byte,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dfa.trans[newsid.as_usize() + usize::from(class)] =
|
||||||
|
old2new(oldnextsid);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Now that we've remapped all the IDs in our states, all that's left
|
||||||
|
// is remapping the special state IDs.
|
||||||
|
let old = nnfa.special();
|
||||||
|
let new = &mut dfa.special;
|
||||||
|
new.max_special_id = old2new(old.max_special_id);
|
||||||
|
new.max_match_id = old2new(old.max_match_id);
|
||||||
|
if anchored.is_anchored() {
|
||||||
|
new.start_unanchored_id = DFA::DEAD;
|
||||||
|
new.start_anchored_id = old2new(old.start_anchored_id);
|
||||||
|
} else {
|
||||||
|
new.start_unanchored_id = old2new(old.start_unanchored_id);
|
||||||
|
new.start_anchored_id = DFA::DEAD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finishes building a DFA that supports BOTH unanchored and anchored
|
||||||
|
/// searches. It works by inter-leaving unanchored states with anchored
|
||||||
|
/// states in the same transition table. This way, we avoid needing to
|
||||||
|
/// re-shuffle states afterward to ensure that our states still look like
|
||||||
|
/// DEAD, MATCH, ..., START-UNANCHORED, START-ANCHORED, NON-MATCH, ...
|
||||||
|
///
|
||||||
|
/// Honestly this is pretty inscrutable... Simplifications are most
|
||||||
|
/// welcome.
|
||||||
|
fn finish_build_both_starts(
|
||||||
|
&self,
|
||||||
|
nnfa: &noncontiguous::NFA,
|
||||||
|
dfa: &mut DFA,
|
||||||
|
) {
|
||||||
|
let stride2 = dfa.stride2;
|
||||||
|
let stride = 1 << stride2;
|
||||||
|
let mut remap_unanchored = vec![DFA::DEAD; nnfa.states().len()];
|
||||||
|
let mut remap_anchored = vec![DFA::DEAD; nnfa.states().len()];
|
||||||
|
let mut is_anchored = vec![false; dfa.state_len];
|
||||||
|
let mut newsid = DFA::DEAD;
|
||||||
|
let next_dfa_id =
|
||||||
|
|sid: StateID| StateID::new_unchecked(sid.as_usize() + stride);
|
||||||
|
for (oldsid, state) in nnfa.states().iter().with_state_ids() {
|
||||||
|
if oldsid == noncontiguous::NFA::DEAD
|
||||||
|
|| oldsid == noncontiguous::NFA::FAIL
|
||||||
|
{
|
||||||
|
remap_unanchored[oldsid] = newsid;
|
||||||
|
remap_anchored[oldsid] = newsid;
|
||||||
|
newsid = next_dfa_id(newsid);
|
||||||
|
} else if oldsid == nnfa.special().start_unanchored_id
|
||||||
|
|| oldsid == nnfa.special().start_anchored_id
|
||||||
|
{
|
||||||
|
if oldsid == nnfa.special().start_unanchored_id {
|
||||||
|
remap_unanchored[oldsid] = newsid;
|
||||||
|
remap_anchored[oldsid] = DFA::DEAD;
|
||||||
|
} else {
|
||||||
|
remap_unanchored[oldsid] = DFA::DEAD;
|
||||||
|
remap_anchored[oldsid] = newsid;
|
||||||
|
is_anchored[newsid.as_usize() >> stride2] = true;
|
||||||
|
}
|
||||||
|
if state.is_match() {
|
||||||
|
dfa.set_matches(newsid, nnfa.iter_matches(oldsid));
|
||||||
|
}
|
||||||
|
sparse_iter(
|
||||||
|
nnfa,
|
||||||
|
oldsid,
|
||||||
|
&dfa.byte_classes,
|
||||||
|
|_, class, oldnextsid| {
|
||||||
|
let class = usize::from(class);
|
||||||
|
if oldnextsid == noncontiguous::NFA::FAIL {
|
||||||
|
dfa.trans[newsid.as_usize() + class] = DFA::DEAD;
|
||||||
|
} else {
|
||||||
|
dfa.trans[newsid.as_usize() + class] = oldnextsid;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
newsid = next_dfa_id(newsid);
|
||||||
|
} else {
|
||||||
|
let unewsid = newsid;
|
||||||
|
newsid = next_dfa_id(newsid);
|
||||||
|
let anewsid = newsid;
|
||||||
|
newsid = next_dfa_id(newsid);
|
||||||
|
|
||||||
|
remap_unanchored[oldsid] = unewsid;
|
||||||
|
remap_anchored[oldsid] = anewsid;
|
||||||
|
is_anchored[anewsid.as_usize() >> stride2] = true;
|
||||||
|
if state.is_match() {
|
||||||
|
dfa.set_matches(unewsid, nnfa.iter_matches(oldsid));
|
||||||
|
dfa.set_matches(anewsid, nnfa.iter_matches(oldsid));
|
||||||
|
}
|
||||||
|
sparse_iter(
|
||||||
|
nnfa,
|
||||||
|
oldsid,
|
||||||
|
&dfa.byte_classes,
|
||||||
|
|byte, class, oldnextsid| {
|
||||||
|
let class = usize::from(class);
|
||||||
|
if oldnextsid == noncontiguous::NFA::FAIL {
|
||||||
|
let oldnextsid =
|
||||||
|
if state.fail() == noncontiguous::NFA::DEAD {
|
||||||
|
noncontiguous::NFA::DEAD
|
||||||
|
} else {
|
||||||
|
nnfa.next_state(
|
||||||
|
Anchored::No,
|
||||||
|
state.fail(),
|
||||||
|
byte,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
dfa.trans[unewsid.as_usize() + class] = oldnextsid;
|
||||||
|
} else {
|
||||||
|
dfa.trans[unewsid.as_usize() + class] = oldnextsid;
|
||||||
|
dfa.trans[anewsid.as_usize() + class] = oldnextsid;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in 0..dfa.state_len {
|
||||||
|
let sid = i << stride2;
|
||||||
|
if is_anchored[i] {
|
||||||
|
for next in dfa.trans[sid..][..stride].iter_mut() {
|
||||||
|
*next = remap_anchored[*next];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for next in dfa.trans[sid..][..stride].iter_mut() {
|
||||||
|
*next = remap_unanchored[*next];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now that we've remapped all the IDs in our states, all that's left
|
||||||
|
// is remapping the special state IDs.
|
||||||
|
let old = nnfa.special();
|
||||||
|
let new = &mut dfa.special;
|
||||||
|
new.max_special_id = remap_anchored[old.max_special_id];
|
||||||
|
new.max_match_id = remap_anchored[old.max_match_id];
|
||||||
|
new.start_unanchored_id = remap_unanchored[old.start_unanchored_id];
|
||||||
|
new.start_anchored_id = remap_anchored[old.start_anchored_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the desired match semantics.
|
||||||
|
///
|
||||||
|
/// This only applies when using [`Builder::build`] and not
|
||||||
|
/// [`Builder::build_from_noncontiguous`].
|
||||||
|
///
|
||||||
|
/// See
|
||||||
|
/// [`AhoCorasickBuilder::match_kind`](crate::AhoCorasickBuilder::match_kind)
|
||||||
|
/// for more documentation and examples.
|
||||||
|
pub fn match_kind(&mut self, kind: MatchKind) -> &mut Builder {
|
||||||
|
self.noncontiguous.match_kind(kind);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable ASCII-aware case insensitive matching.
|
||||||
|
///
|
||||||
|
/// This only applies when using [`Builder::build`] and not
|
||||||
|
/// [`Builder::build_from_noncontiguous`].
|
||||||
|
///
|
||||||
|
/// See
|
||||||
|
/// [`AhoCorasickBuilder::ascii_case_insensitive`](crate::AhoCorasickBuilder::ascii_case_insensitive)
|
||||||
|
/// for more documentation and examples.
|
||||||
|
pub fn ascii_case_insensitive(&mut self, yes: bool) -> &mut Builder {
|
||||||
|
self.noncontiguous.ascii_case_insensitive(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable heuristic prefilter optimizations.
|
||||||
|
///
|
||||||
|
/// This only applies when using [`Builder::build`] and not
|
||||||
|
/// [`Builder::build_from_noncontiguous`].
|
||||||
|
///
|
||||||
|
/// See
|
||||||
|
/// [`AhoCorasickBuilder::prefilter`](crate::AhoCorasickBuilder::prefilter)
|
||||||
|
/// for more documentation and examples.
|
||||||
|
pub fn prefilter(&mut self, yes: bool) -> &mut Builder {
|
||||||
|
self.noncontiguous.prefilter(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the starting state configuration for the automaton.
|
||||||
|
///
|
||||||
|
/// See
|
||||||
|
/// [`AhoCorasickBuilder::start_kind`](crate::AhoCorasickBuilder::start_kind)
|
||||||
|
/// for more documentation and examples.
|
||||||
|
pub fn start_kind(&mut self, kind: StartKind) -> &mut Builder {
|
||||||
|
self.start_kind = kind;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A debug setting for whether to attempt to shrink the size of the
|
||||||
|
/// automaton's alphabet or not.
|
||||||
|
///
|
||||||
|
/// This should never be enabled unless you're debugging an automaton.
|
||||||
|
/// Namely, disabling byte classes makes transitions easier to reason
|
||||||
|
/// about, since they use the actual bytes instead of equivalence classes.
|
||||||
|
/// Disabling this confers no performance benefit at search time.
|
||||||
|
///
|
||||||
|
/// See
|
||||||
|
/// [`AhoCorasickBuilder::byte_classes`](crate::AhoCorasickBuilder::byte_classes)
|
||||||
|
/// for more documentation and examples.
|
||||||
|
pub fn byte_classes(&mut self, yes: bool) -> &mut Builder {
|
||||||
|
self.byte_classes = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all possible equivalence class transitions in this state.
|
||||||
|
/// The closure is called for all transitions with a distinct equivalence
|
||||||
|
/// class, even those not explicitly represented in this sparse state. For
|
||||||
|
/// any implicitly defined transitions, the given closure is called with
|
||||||
|
/// the fail state ID.
|
||||||
|
///
|
||||||
|
/// The closure is guaranteed to be called precisely
|
||||||
|
/// `byte_classes.alphabet_len()` times, once for every possible class in
|
||||||
|
/// ascending order.
|
||||||
|
fn sparse_iter<F: FnMut(u8, u8, StateID)>(
|
||||||
|
nnfa: &noncontiguous::NFA,
|
||||||
|
oldsid: StateID,
|
||||||
|
classes: &ByteClasses,
|
||||||
|
mut f: F,
|
||||||
|
) {
|
||||||
|
let mut prev_class = None;
|
||||||
|
let mut byte = 0usize;
|
||||||
|
for t in nnfa.iter_trans(oldsid) {
|
||||||
|
while byte < usize::from(t.byte()) {
|
||||||
|
let rep = byte.as_u8();
|
||||||
|
let class = classes.get(rep);
|
||||||
|
byte += 1;
|
||||||
|
if prev_class != Some(class) {
|
||||||
|
f(rep, class, noncontiguous::NFA::FAIL);
|
||||||
|
prev_class = Some(class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let rep = t.byte();
|
||||||
|
let class = classes.get(rep);
|
||||||
|
byte += 1;
|
||||||
|
if prev_class != Some(class) {
|
||||||
|
f(rep, class, t.next());
|
||||||
|
prev_class = Some(class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for b in byte..=255 {
|
||||||
|
let rep = b.as_u8();
|
||||||
|
let class = classes.get(rep);
|
||||||
|
if prev_class != Some(class) {
|
||||||
|
f(rep, class, noncontiguous::NFA::FAIL);
|
||||||
|
prev_class = Some(class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
326
third_party/crates/aho-corasick-1.1.5/src/lib.rs
vendored
Normal file
326
third_party/crates/aho-corasick-1.1.5/src/lib.rs
vendored
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
/*!
|
||||||
|
A library for finding occurrences of many patterns at once. This library
|
||||||
|
provides multiple pattern search principally through an implementation of the
|
||||||
|
[Aho-Corasick algorithm](https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm),
|
||||||
|
which builds a fast finite state machine for executing searches in linear time.
|
||||||
|
|
||||||
|
Additionally, this library provides a number of configuration options for
|
||||||
|
building the automaton that permit controlling the space versus time trade
|
||||||
|
off. Other features include simple ASCII case insensitive matching, finding
|
||||||
|
overlapping matches, replacements, searching streams and even searching and
|
||||||
|
replacing text in streams.
|
||||||
|
|
||||||
|
Finally, unlike most other Aho-Corasick implementations, this one
|
||||||
|
supports enabling [leftmost-first](MatchKind::LeftmostFirst) or
|
||||||
|
[leftmost-longest](MatchKind::LeftmostLongest) match semantics, using a
|
||||||
|
(seemingly) novel alternative construction algorithm. For more details on what
|
||||||
|
match semantics means, see the [`MatchKind`] type.
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This section gives a brief overview of the primary types in this crate:
|
||||||
|
|
||||||
|
* [`AhoCorasick`] is the primary type and represents an Aho-Corasick automaton.
|
||||||
|
This is the type you use to execute searches.
|
||||||
|
* [`AhoCorasickBuilder`] can be used to build an Aho-Corasick automaton, and
|
||||||
|
supports configuring a number of options.
|
||||||
|
* [`Match`] represents a single match reported by an Aho-Corasick automaton.
|
||||||
|
Each match has two pieces of information: the pattern that matched and the
|
||||||
|
start and end byte offsets corresponding to the position in the haystack at
|
||||||
|
which it matched.
|
||||||
|
|
||||||
|
# Example: basic searching
|
||||||
|
|
||||||
|
This example shows how to search for occurrences of multiple patterns
|
||||||
|
simultaneously. Each match includes the pattern that matched along with the
|
||||||
|
byte offsets of the match.
|
||||||
|
|
||||||
|
```
|
||||||
|
use aho_corasick::{AhoCorasick, PatternID};
|
||||||
|
|
||||||
|
let patterns = &["apple", "maple", "Snapple"];
|
||||||
|
let haystack = "Nobody likes maple in their apple flavored Snapple.";
|
||||||
|
|
||||||
|
let ac = AhoCorasick::new(patterns).unwrap();
|
||||||
|
let mut matches = vec![];
|
||||||
|
for mat in ac.find_iter(haystack) {
|
||||||
|
matches.push((mat.pattern(), mat.start(), mat.end()));
|
||||||
|
}
|
||||||
|
assert_eq!(matches, vec![
|
||||||
|
(PatternID::must(1), 13, 18),
|
||||||
|
(PatternID::must(0), 28, 33),
|
||||||
|
(PatternID::must(2), 43, 50),
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Example: case insensitivity
|
||||||
|
|
||||||
|
This is like the previous example, but matches `Snapple` case insensitively
|
||||||
|
using `AhoCorasickBuilder`:
|
||||||
|
|
||||||
|
```
|
||||||
|
use aho_corasick::{AhoCorasick, PatternID};
|
||||||
|
|
||||||
|
let patterns = &["apple", "maple", "snapple"];
|
||||||
|
let haystack = "Nobody likes maple in their apple flavored Snapple.";
|
||||||
|
|
||||||
|
let ac = AhoCorasick::builder()
|
||||||
|
.ascii_case_insensitive(true)
|
||||||
|
.build(patterns)
|
||||||
|
.unwrap();
|
||||||
|
let mut matches = vec![];
|
||||||
|
for mat in ac.find_iter(haystack) {
|
||||||
|
matches.push((mat.pattern(), mat.start(), mat.end()));
|
||||||
|
}
|
||||||
|
assert_eq!(matches, vec![
|
||||||
|
(PatternID::must(1), 13, 18),
|
||||||
|
(PatternID::must(0), 28, 33),
|
||||||
|
(PatternID::must(2), 43, 50),
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Example: replacing matches in a stream
|
||||||
|
|
||||||
|
This example shows how to execute a search and replace on a stream without
|
||||||
|
loading the entire stream into memory first.
|
||||||
|
|
||||||
|
```
|
||||||
|
# #[cfg(feature = "std")] {
|
||||||
|
use aho_corasick::AhoCorasick;
|
||||||
|
|
||||||
|
# fn example() -> Result<(), std::io::Error> {
|
||||||
|
let patterns = &["fox", "brown", "quick"];
|
||||||
|
let replace_with = &["sloth", "grey", "slow"];
|
||||||
|
|
||||||
|
// In a real example, these might be `std::fs::File`s instead. All you need to
|
||||||
|
// do is supply a pair of `std::io::Read` and `std::io::Write` implementations.
|
||||||
|
let rdr = "The quick brown fox.";
|
||||||
|
let mut wtr = vec![];
|
||||||
|
|
||||||
|
let ac = AhoCorasick::new(patterns).unwrap();
|
||||||
|
ac.try_stream_replace_all(rdr.as_bytes(), &mut wtr, replace_with)?;
|
||||||
|
assert_eq!(b"The slow grey sloth.".to_vec(), wtr);
|
||||||
|
# Ok(()) }; example().unwrap()
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
# Example: finding the leftmost first match
|
||||||
|
|
||||||
|
In the textbook description of Aho-Corasick, its formulation is typically
|
||||||
|
structured such that it reports all possible matches, even when they overlap
|
||||||
|
with another. In many cases, overlapping matches may not be desired, such as
|
||||||
|
the case of finding all successive non-overlapping matches like you might with
|
||||||
|
a standard regular expression.
|
||||||
|
|
||||||
|
Unfortunately the "obvious" way to modify the Aho-Corasick algorithm to do
|
||||||
|
this doesn't always work in the expected way, since it will report matches as
|
||||||
|
soon as they are seen. For example, consider matching the regex `Samwise|Sam`
|
||||||
|
against the text `Samwise`. Most regex engines (that are Perl-like, or
|
||||||
|
non-POSIX) will report `Samwise` as a match, but the standard Aho-Corasick
|
||||||
|
algorithm modified for reporting non-overlapping matches will report `Sam`.
|
||||||
|
|
||||||
|
A novel contribution of this library is the ability to change the match
|
||||||
|
semantics of Aho-Corasick (without additional search time overhead) such that
|
||||||
|
`Samwise` is reported instead. For example, here's the standard approach:
|
||||||
|
|
||||||
|
```
|
||||||
|
use aho_corasick::AhoCorasick;
|
||||||
|
|
||||||
|
let patterns = &["Samwise", "Sam"];
|
||||||
|
let haystack = "Samwise";
|
||||||
|
|
||||||
|
let ac = AhoCorasick::new(patterns).unwrap();
|
||||||
|
let mat = ac.find(haystack).expect("should have a match");
|
||||||
|
assert_eq!("Sam", &haystack[mat.start()..mat.end()]);
|
||||||
|
```
|
||||||
|
|
||||||
|
And now here's the leftmost-first version, which matches how a Perl-like
|
||||||
|
regex will work:
|
||||||
|
|
||||||
|
```
|
||||||
|
use aho_corasick::{AhoCorasick, MatchKind};
|
||||||
|
|
||||||
|
let patterns = &["Samwise", "Sam"];
|
||||||
|
let haystack = "Samwise";
|
||||||
|
|
||||||
|
let ac = AhoCorasick::builder()
|
||||||
|
.match_kind(MatchKind::LeftmostFirst)
|
||||||
|
.build(patterns)
|
||||||
|
.unwrap();
|
||||||
|
let mat = ac.find(haystack).expect("should have a match");
|
||||||
|
assert_eq!("Samwise", &haystack[mat.start()..mat.end()]);
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition to leftmost-first semantics, this library also supports
|
||||||
|
leftmost-longest semantics, which match the POSIX behavior of a regular
|
||||||
|
expression alternation. See [`MatchKind`] for more details.
|
||||||
|
|
||||||
|
# Prefilters
|
||||||
|
|
||||||
|
While an Aho-Corasick automaton can perform admirably when compared to more
|
||||||
|
naive solutions, it is generally slower than more specialized algorithms that
|
||||||
|
are accelerated using vector instructions such as SIMD.
|
||||||
|
|
||||||
|
For that reason, this library will internally use a "prefilter" to attempt
|
||||||
|
to accelerate searches when possible. Currently, this library has several
|
||||||
|
different algorithms it might use depending on the patterns provided. Once the
|
||||||
|
number of patterns gets too big, prefilters are no longer used.
|
||||||
|
|
||||||
|
While a prefilter is generally good to have on by default since it works
|
||||||
|
well in the common case, it can lead to less predictable or even sub-optimal
|
||||||
|
performance in some cases. For that reason, prefilters can be explicitly
|
||||||
|
disabled via [`AhoCorasickBuilder::prefilter`].
|
||||||
|
|
||||||
|
# Lower level APIs
|
||||||
|
|
||||||
|
This crate also provides several sub-modules that collectively expose many of
|
||||||
|
the implementation details of the main [`AhoCorasick`] type. Most users of this
|
||||||
|
library can completely ignore the submodules and their contents, but if you
|
||||||
|
needed finer grained control, some parts of them may be useful to you. Here is
|
||||||
|
a brief overview of each and why you might want to use them:
|
||||||
|
|
||||||
|
* The [`packed`] sub-module contains a lower level API for using fast
|
||||||
|
vectorized routines for finding a small number of patterns in a haystack.
|
||||||
|
You might want to use this API when you want to completely side-step using
|
||||||
|
Aho-Corasick automata. Otherwise, the fast vectorized routines are used
|
||||||
|
automatically as prefilters for `AhoCorasick` searches whenever possible.
|
||||||
|
* The [`automaton`] sub-module provides a lower level finite state
|
||||||
|
machine interface that the various Aho-Corasick implementations in
|
||||||
|
this crate implement. This sub-module's main contribution is the
|
||||||
|
[`Automaton`](automaton::Automaton) trait, which permits manually walking the
|
||||||
|
state transitions of an Aho-Corasick automaton.
|
||||||
|
* The [`dfa`] and [`nfa`] sub-modules provide DFA and NFA implementations of
|
||||||
|
the aforementioned `Automaton` trait. The main reason one might want to use
|
||||||
|
these sub-modules is to get access to a type that implements the `Automaton`
|
||||||
|
trait. (The top-level `AhoCorasick` type does not implement the `Automaton`
|
||||||
|
trait.)
|
||||||
|
|
||||||
|
As mentioned above, if you aren't sure whether you need these sub-modules,
|
||||||
|
you should be able to safely ignore them and just focus on the [`AhoCorasick`]
|
||||||
|
type.
|
||||||
|
|
||||||
|
# Crate features
|
||||||
|
|
||||||
|
This crate exposes a few features for controlling dependency usage and whether
|
||||||
|
this crate can be used without the standard library.
|
||||||
|
|
||||||
|
* **std** -
|
||||||
|
Enables support for the standard library. This feature is enabled by
|
||||||
|
default. When disabled, only `core` and `alloc` are used. At an API
|
||||||
|
level, enabling `std` enables `std::error::Error` trait impls for the
|
||||||
|
various error types, and higher level stream search routines such as
|
||||||
|
[`AhoCorasick::try_stream_find_iter`]. But the `std` feature is also required
|
||||||
|
to enable vectorized prefilters. Prefilters can greatly accelerate searches,
|
||||||
|
but generally only apply when the number of patterns is small (less than
|
||||||
|
~100).
|
||||||
|
* **perf-literal** -
|
||||||
|
Enables support for literal prefilters that use vectorized routines from
|
||||||
|
external crates. This feature is enabled by default. If you're only using
|
||||||
|
Aho-Corasick for large numbers of patterns or otherwise can abide lower
|
||||||
|
throughput when searching with a small number of patterns, then it is
|
||||||
|
reasonable to disable this feature.
|
||||||
|
* **logging** -
|
||||||
|
Enables a dependency on the `log` crate and emits messages to aide in
|
||||||
|
diagnostics. This feature is disabled by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![deny(rustdoc::broken_intra_doc_links)]
|
||||||
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
#[cfg(any(test, feature = "std"))]
|
||||||
|
extern crate std;
|
||||||
|
|
||||||
|
#[cfg(doctest)]
|
||||||
|
doc_comment::doctest!("../README.md");
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use crate::ahocorasick::StreamFindIter;
|
||||||
|
pub use crate::{
|
||||||
|
ahocorasick::{
|
||||||
|
AhoCorasick, AhoCorasickBuilder, AhoCorasickKind, FindIter,
|
||||||
|
FindOverlappingIter,
|
||||||
|
},
|
||||||
|
util::{
|
||||||
|
error::{BuildError, MatchError, MatchErrorKind},
|
||||||
|
primitives::{PatternID, PatternIDError},
|
||||||
|
search::{Anchored, Input, Match, MatchKind, Span, StartKind},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
|
mod ahocorasick;
|
||||||
|
pub mod automaton;
|
||||||
|
pub mod dfa;
|
||||||
|
pub mod nfa;
|
||||||
|
pub mod packed;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
// I wrote out the module for implementing fst::Automaton only to later realize
|
||||||
|
// that this would make fst a public dependency and fst is not at 1.0 yet. I
|
||||||
|
// decided to just keep the code in tree, but build it only during tests.
|
||||||
|
//
|
||||||
|
// TODO: I think I've changed my mind again. I'm considering pushing it out
|
||||||
|
// into either a separate crate or into 'fst' directly as an optional feature.
|
||||||
|
// #[cfg(test)]
|
||||||
|
// #[allow(dead_code)]
|
||||||
|
// mod transducer;
|
||||||
|
pub(crate) mod util;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod testoibits {
|
||||||
|
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn assert_all<T: Send + Sync + UnwindSafe + RefUnwindSafe>() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oibits_main() {
|
||||||
|
assert_all::<AhoCorasick>();
|
||||||
|
assert_all::<AhoCorasickBuilder>();
|
||||||
|
assert_all::<AhoCorasickKind>();
|
||||||
|
assert_all::<FindIter>();
|
||||||
|
assert_all::<FindOverlappingIter>();
|
||||||
|
|
||||||
|
assert_all::<BuildError>();
|
||||||
|
assert_all::<MatchError>();
|
||||||
|
assert_all::<MatchErrorKind>();
|
||||||
|
|
||||||
|
assert_all::<Anchored>();
|
||||||
|
assert_all::<Input>();
|
||||||
|
assert_all::<Match>();
|
||||||
|
assert_all::<MatchKind>();
|
||||||
|
assert_all::<Span>();
|
||||||
|
assert_all::<StartKind>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oibits_automaton() {
|
||||||
|
use crate::{automaton, dfa::DFA};
|
||||||
|
|
||||||
|
assert_all::<automaton::FindIter<DFA>>();
|
||||||
|
assert_all::<automaton::FindOverlappingIter<DFA>>();
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
assert_all::<automaton::StreamFindIter<DFA, std::io::Stdin>>();
|
||||||
|
assert_all::<automaton::OverlappingState>();
|
||||||
|
|
||||||
|
assert_all::<automaton::Prefilter>();
|
||||||
|
assert_all::<automaton::Candidate>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oibits_packed() {
|
||||||
|
use crate::packed;
|
||||||
|
|
||||||
|
assert_all::<packed::Config>();
|
||||||
|
assert_all::<packed::Builder>();
|
||||||
|
assert_all::<packed::Searcher>();
|
||||||
|
assert_all::<packed::FindIter>();
|
||||||
|
assert_all::<packed::MatchKind>();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
third_party/crates/aho-corasick-1.1.5/src/macros.rs
vendored
Normal file
18
third_party/crates/aho-corasick-1.1.5/src/macros.rs
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#![allow(unused_macros)]
|
||||||
|
|
||||||
|
macro_rules! log {
|
||||||
|
($($tt:tt)*) => {
|
||||||
|
#[cfg(feature = "logging")]
|
||||||
|
{
|
||||||
|
$($tt)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! debug {
|
||||||
|
($($tt:tt)*) => { log!(log::debug!($($tt)*)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! trace {
|
||||||
|
($($tt:tt)*) => { log!(log::trace!($($tt)*)) }
|
||||||
|
}
|
||||||
1141
third_party/crates/aho-corasick-1.1.5/src/nfa/contiguous.rs
vendored
Normal file
1141
third_party/crates/aho-corasick-1.1.5/src/nfa/contiguous.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
40
third_party/crates/aho-corasick-1.1.5/src/nfa/mod.rs
vendored
Normal file
40
third_party/crates/aho-corasick-1.1.5/src/nfa/mod.rs
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*!
|
||||||
|
Provides direct access to NFA implementations of Aho-Corasick.
|
||||||
|
|
||||||
|
The principle characteristic of an NFA in this crate is that it may
|
||||||
|
transition through multiple states per byte of haystack. In Aho-Corasick
|
||||||
|
parlance, NFAs follow failure transitions during a search. In contrast,
|
||||||
|
a [`DFA`](crate::dfa::DFA) pre-computes all failure transitions during
|
||||||
|
compilation at the expense of a much bigger memory footprint.
|
||||||
|
|
||||||
|
Currently, there are two NFA implementations provided: noncontiguous and
|
||||||
|
contiguous. The names reflect their internal representation, and consequently,
|
||||||
|
the trade offs associated with them:
|
||||||
|
|
||||||
|
* A [`noncontiguous::NFA`] uses a separate allocation for every NFA state to
|
||||||
|
represent its transitions in a sparse format. This is ideal for building an
|
||||||
|
NFA, since it cheaply permits different states to have a different number of
|
||||||
|
transitions. A noncontiguous NFA is where the main Aho-Corasick construction
|
||||||
|
algorithm is implemented. All other Aho-Corasick implementations are built by
|
||||||
|
first constructing a noncontiguous NFA.
|
||||||
|
* A [`contiguous::NFA`] is uses a single allocation to represent all states,
|
||||||
|
while still encoding most states as sparse states but permitting states near
|
||||||
|
the starting state to have a dense representation. The dense representation
|
||||||
|
uses more memory, but permits computing transitions during a search more
|
||||||
|
quickly. By only making the most active states dense (the states near the
|
||||||
|
starting state), a contiguous NFA better balances memory usage with search
|
||||||
|
speed. The single contiguous allocation also uses less overhead per state and
|
||||||
|
enables compression tricks where most states only use 8 bytes of heap memory.
|
||||||
|
|
||||||
|
When given the choice between these two, you almost always want to pick a
|
||||||
|
contiguous NFA. It takes only a little longer to build, but both its memory
|
||||||
|
usage and search speed are typically much better than a noncontiguous NFA. A
|
||||||
|
noncontiguous NFA is useful when prioritizing build times, or when there are
|
||||||
|
so many patterns that a contiguous NFA could not be built. (Currently, because
|
||||||
|
of both memory and search speed improvements, a contiguous NFA has a smaller
|
||||||
|
internal limit on the total number of NFA states it can represent. But you
|
||||||
|
would likely need to have hundreds of thousands or even millions of patterns
|
||||||
|
before you hit this limit.)
|
||||||
|
*/
|
||||||
|
pub mod contiguous;
|
||||||
|
pub mod noncontiguous;
|
||||||
1762
third_party/crates/aho-corasick-1.1.5/src/nfa/noncontiguous.rs
vendored
Normal file
1762
third_party/crates/aho-corasick-1.1.5/src/nfa/noncontiguous.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
691
third_party/crates/aho-corasick-1.1.5/src/packed/api.rs
vendored
Normal file
691
third_party/crates/aho-corasick-1.1.5/src/packed/api.rs
vendored
Normal file
@@ -0,0 +1,691 @@
|
|||||||
|
use alloc::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
packed::{pattern::Patterns, rabinkarp::RabinKarp, teddy},
|
||||||
|
util::search::{Match, Span},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This is a limit placed on the total number of patterns we're willing to try
|
||||||
|
/// and match at once. As more sophisticated algorithms are added, this number
|
||||||
|
/// may be increased.
|
||||||
|
const PATTERN_LIMIT: usize = 128;
|
||||||
|
|
||||||
|
/// A knob for controlling the match semantics of a packed multiple string
|
||||||
|
/// searcher.
|
||||||
|
///
|
||||||
|
/// This differs from the [`MatchKind`](crate::MatchKind) type in the top-level
|
||||||
|
/// crate module in that it doesn't support "standard" match semantics,
|
||||||
|
/// and instead only supports leftmost-first or leftmost-longest. Namely,
|
||||||
|
/// "standard" semantics cannot be easily supported by packed searchers.
|
||||||
|
///
|
||||||
|
/// For more information on the distinction between leftmost-first and
|
||||||
|
/// leftmost-longest, see the docs on the top-level `MatchKind` type.
|
||||||
|
///
|
||||||
|
/// Unlike the top-level `MatchKind` type, the default match semantics for this
|
||||||
|
/// type are leftmost-first.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum MatchKind {
|
||||||
|
/// Use leftmost-first match semantics, which reports leftmost matches.
|
||||||
|
/// When there are multiple possible leftmost matches, the match
|
||||||
|
/// corresponding to the pattern that appeared earlier when constructing
|
||||||
|
/// the automaton is reported.
|
||||||
|
///
|
||||||
|
/// This is the default.
|
||||||
|
LeftmostFirst,
|
||||||
|
/// Use leftmost-longest match semantics, which reports leftmost matches.
|
||||||
|
/// When there are multiple possible leftmost matches, the longest match
|
||||||
|
/// is chosen.
|
||||||
|
LeftmostLongest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MatchKind {
|
||||||
|
fn default() -> MatchKind {
|
||||||
|
MatchKind::LeftmostFirst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The configuration for a packed multiple pattern searcher.
|
||||||
|
///
|
||||||
|
/// The configuration is currently limited only to being able to select the
|
||||||
|
/// match semantics (leftmost-first or leftmost-longest) of a searcher. In the
|
||||||
|
/// future, more knobs may be made available.
|
||||||
|
///
|
||||||
|
/// A configuration produces a [`packed::Builder`](Builder), which in turn can
|
||||||
|
/// be used to construct a [`packed::Searcher`](Searcher) for searching.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This example shows how to use leftmost-longest semantics instead of the
|
||||||
|
/// default (leftmost-first).
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{packed::{Config, MatchKind}, PatternID};
|
||||||
|
///
|
||||||
|
/// # fn example() -> Option<()> {
|
||||||
|
/// let searcher = Config::new()
|
||||||
|
/// .match_kind(MatchKind::LeftmostLongest)
|
||||||
|
/// .builder()
|
||||||
|
/// .add("foo")
|
||||||
|
/// .add("foobar")
|
||||||
|
/// .build()?;
|
||||||
|
/// let matches: Vec<PatternID> = searcher
|
||||||
|
/// .find_iter("foobar")
|
||||||
|
/// .map(|mat| mat.pattern())
|
||||||
|
/// .collect();
|
||||||
|
/// assert_eq!(vec![PatternID::must(1)], matches);
|
||||||
|
/// # Some(()) }
|
||||||
|
/// # if cfg!(all(feature = "std", any(
|
||||||
|
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||||
|
/// # ))) {
|
||||||
|
/// # example().unwrap()
|
||||||
|
/// # } else {
|
||||||
|
/// # assert!(example().is_none());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
kind: MatchKind,
|
||||||
|
force: Option<ForceAlgorithm>,
|
||||||
|
only_teddy_fat: Option<bool>,
|
||||||
|
only_teddy_256bit: Option<bool>,
|
||||||
|
heuristic_pattern_limits: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An internal option for forcing the use of a particular packed algorithm.
|
||||||
|
///
|
||||||
|
/// When an algorithm is forced, if a searcher could not be constructed for it,
|
||||||
|
/// then no searcher will be returned even if an alternative algorithm would
|
||||||
|
/// work.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum ForceAlgorithm {
|
||||||
|
Teddy,
|
||||||
|
RabinKarp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Config {
|
||||||
|
Config::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
/// Create a new default configuration. A default configuration uses
|
||||||
|
/// leftmost-first match semantics.
|
||||||
|
pub fn new() -> Config {
|
||||||
|
Config {
|
||||||
|
kind: MatchKind::LeftmostFirst,
|
||||||
|
force: None,
|
||||||
|
only_teddy_fat: None,
|
||||||
|
only_teddy_256bit: None,
|
||||||
|
heuristic_pattern_limits: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a packed builder from this configuration. The builder can be
|
||||||
|
/// used to accumulate patterns and create a [`Searcher`] from them.
|
||||||
|
pub fn builder(&self) -> Builder {
|
||||||
|
Builder::from_config(self.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the match semantics for this configuration.
|
||||||
|
pub fn match_kind(&mut self, kind: MatchKind) -> &mut Config {
|
||||||
|
self.kind = kind;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An undocumented method for forcing the use of the Teddy algorithm.
|
||||||
|
///
|
||||||
|
/// This is only exposed for more precise testing and benchmarks. Callers
|
||||||
|
/// should not use it as it is not part of the API stability guarantees of
|
||||||
|
/// this crate.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn only_teddy(&mut self, yes: bool) -> &mut Config {
|
||||||
|
if yes {
|
||||||
|
self.force = Some(ForceAlgorithm::Teddy);
|
||||||
|
} else {
|
||||||
|
self.force = None;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An undocumented method for forcing the use of the Fat Teddy algorithm.
|
||||||
|
///
|
||||||
|
/// This is only exposed for more precise testing and benchmarks. Callers
|
||||||
|
/// should not use it as it is not part of the API stability guarantees of
|
||||||
|
/// this crate.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn only_teddy_fat(&mut self, yes: Option<bool>) -> &mut Config {
|
||||||
|
self.only_teddy_fat = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An undocumented method for forcing the use of SSE (`Some(false)`) or
|
||||||
|
/// AVX (`Some(true)`) algorithms.
|
||||||
|
///
|
||||||
|
/// This is only exposed for more precise testing and benchmarks. Callers
|
||||||
|
/// should not use it as it is not part of the API stability guarantees of
|
||||||
|
/// this crate.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn only_teddy_256bit(&mut self, yes: Option<bool>) -> &mut Config {
|
||||||
|
self.only_teddy_256bit = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An undocumented method for forcing the use of the Rabin-Karp algorithm.
|
||||||
|
///
|
||||||
|
/// This is only exposed for more precise testing and benchmarks. Callers
|
||||||
|
/// should not use it as it is not part of the API stability guarantees of
|
||||||
|
/// this crate.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn only_rabin_karp(&mut self, yes: bool) -> &mut Config {
|
||||||
|
if yes {
|
||||||
|
self.force = Some(ForceAlgorithm::RabinKarp);
|
||||||
|
} else {
|
||||||
|
self.force = None;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request that heuristic limitations on the number of patterns be
|
||||||
|
/// employed. This useful to disable for benchmarking where one wants to
|
||||||
|
/// explore how Teddy performs on large number of patterns even if the
|
||||||
|
/// heuristics would otherwise refuse construction.
|
||||||
|
///
|
||||||
|
/// This is enabled by default.
|
||||||
|
pub fn heuristic_pattern_limits(&mut self, yes: bool) -> &mut Config {
|
||||||
|
self.heuristic_pattern_limits = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A builder for constructing a packed searcher from a collection of patterns.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This example shows how to use a builder to construct a searcher. By
|
||||||
|
/// default, leftmost-first match semantics are used.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{packed::{Builder, MatchKind}, PatternID};
|
||||||
|
///
|
||||||
|
/// # fn example() -> Option<()> {
|
||||||
|
/// let searcher = Builder::new()
|
||||||
|
/// .add("foobar")
|
||||||
|
/// .add("foo")
|
||||||
|
/// .build()?;
|
||||||
|
/// let matches: Vec<PatternID> = searcher
|
||||||
|
/// .find_iter("foobar")
|
||||||
|
/// .map(|mat| mat.pattern())
|
||||||
|
/// .collect();
|
||||||
|
/// assert_eq!(vec![PatternID::ZERO], matches);
|
||||||
|
/// # Some(()) }
|
||||||
|
/// # if cfg!(all(feature = "std", any(
|
||||||
|
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||||
|
/// # ))) {
|
||||||
|
/// # example().unwrap()
|
||||||
|
/// # } else {
|
||||||
|
/// # assert!(example().is_none());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Builder {
|
||||||
|
/// The configuration of this builder and subsequent matcher.
|
||||||
|
config: Config,
|
||||||
|
/// Set to true if the builder detects that a matcher cannot be built.
|
||||||
|
inert: bool,
|
||||||
|
/// The patterns provided by the caller.
|
||||||
|
patterns: Patterns,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
/// Create a new builder for constructing a multi-pattern searcher. This
|
||||||
|
/// constructor uses the default configuration.
|
||||||
|
pub fn new() -> Builder {
|
||||||
|
Builder::from_config(Config::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_config(config: Config) -> Builder {
|
||||||
|
Builder { config, inert: false, patterns: Patterns::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a searcher from the patterns added to this builder so far.
|
||||||
|
pub fn build(&self) -> Option<Searcher> {
|
||||||
|
if self.inert || self.patterns.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut patterns = self.patterns.clone();
|
||||||
|
patterns.set_match_kind(self.config.kind);
|
||||||
|
let patterns = Arc::new(patterns);
|
||||||
|
let rabinkarp = RabinKarp::new(&patterns);
|
||||||
|
// Effectively, we only want to return a searcher if we can use Teddy,
|
||||||
|
// since Teddy is our only fast packed searcher at the moment.
|
||||||
|
// Rabin-Karp is only used when searching haystacks smaller than what
|
||||||
|
// Teddy can support. Thus, the only way to get a Rabin-Karp searcher
|
||||||
|
// is to force it using undocumented APIs (for tests/benchmarks).
|
||||||
|
let (search_kind, minimum_len) = match self.config.force {
|
||||||
|
None | Some(ForceAlgorithm::Teddy) => {
|
||||||
|
debug!("trying to build Teddy packed matcher");
|
||||||
|
let teddy = match self.build_teddy(Arc::clone(&patterns)) {
|
||||||
|
None => return None,
|
||||||
|
Some(teddy) => teddy,
|
||||||
|
};
|
||||||
|
let minimum_len = teddy.minimum_len();
|
||||||
|
(SearchKind::Teddy(teddy), minimum_len)
|
||||||
|
}
|
||||||
|
Some(ForceAlgorithm::RabinKarp) => {
|
||||||
|
debug!("using Rabin-Karp packed matcher");
|
||||||
|
(SearchKind::RabinKarp, 0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(Searcher { patterns, rabinkarp, search_kind, minimum_len })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_teddy(&self, patterns: Arc<Patterns>) -> Option<teddy::Searcher> {
|
||||||
|
teddy::Builder::new()
|
||||||
|
.only_256bit(self.config.only_teddy_256bit)
|
||||||
|
.only_fat(self.config.only_teddy_fat)
|
||||||
|
.heuristic_pattern_limits(self.config.heuristic_pattern_limits)
|
||||||
|
.build(patterns)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add the given pattern to this set to match.
|
||||||
|
///
|
||||||
|
/// The order in which patterns are added is significant. Namely, when
|
||||||
|
/// using leftmost-first match semantics, then when multiple patterns can
|
||||||
|
/// match at a particular location, the pattern that was added first is
|
||||||
|
/// used as the match.
|
||||||
|
///
|
||||||
|
/// If the number of patterns added exceeds the amount supported by packed
|
||||||
|
/// searchers, then the builder will stop accumulating patterns and render
|
||||||
|
/// itself inert. At this point, constructing a searcher will always return
|
||||||
|
/// `None`.
|
||||||
|
pub fn add<P: AsRef<[u8]>>(&mut self, pattern: P) -> &mut Builder {
|
||||||
|
if self.inert {
|
||||||
|
return self;
|
||||||
|
} else if self.patterns.len() >= PATTERN_LIMIT {
|
||||||
|
self.inert = true;
|
||||||
|
self.patterns.reset();
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
// Just in case PATTERN_LIMIT increases beyond u16::MAX.
|
||||||
|
assert!(self.patterns.len() <= core::u16::MAX as usize);
|
||||||
|
|
||||||
|
let pattern = pattern.as_ref();
|
||||||
|
if pattern.is_empty() {
|
||||||
|
self.inert = true;
|
||||||
|
self.patterns.reset();
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
self.patterns.add(pattern);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add the given iterator of patterns to this set to match.
|
||||||
|
///
|
||||||
|
/// The iterator must yield elements that can be converted into a `&[u8]`.
|
||||||
|
///
|
||||||
|
/// The order in which patterns are added is significant. Namely, when
|
||||||
|
/// using leftmost-first match semantics, then when multiple patterns can
|
||||||
|
/// match at a particular location, the pattern that was added first is
|
||||||
|
/// used as the match.
|
||||||
|
///
|
||||||
|
/// If the number of patterns added exceeds the amount supported by packed
|
||||||
|
/// searchers, then the builder will stop accumulating patterns and render
|
||||||
|
/// itself inert. At this point, constructing a searcher will always return
|
||||||
|
/// `None`.
|
||||||
|
pub fn extend<I, P>(&mut self, patterns: I) -> &mut Builder
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = P>,
|
||||||
|
P: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
for p in patterns {
|
||||||
|
self.add(p);
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of patterns added to this builder.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.patterns.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length, in bytes, of the shortest pattern added.
|
||||||
|
pub fn minimum_len(&self) -> usize {
|
||||||
|
self.patterns.minimum_len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Builder {
|
||||||
|
fn default() -> Builder {
|
||||||
|
Builder::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A packed searcher for quickly finding occurrences of multiple patterns.
|
||||||
|
///
|
||||||
|
/// If callers need more flexible construction, or if one wants to change the
|
||||||
|
/// match semantics (either leftmost-first or leftmost-longest), then one can
|
||||||
|
/// use the [`Config`] and/or [`Builder`] types for more fine grained control.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This example shows how to create a searcher from an iterator of patterns.
|
||||||
|
/// By default, leftmost-first match semantics are used.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
|
||||||
|
///
|
||||||
|
/// # fn example() -> Option<()> {
|
||||||
|
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||||
|
/// let matches: Vec<PatternID> = searcher
|
||||||
|
/// .find_iter("foobar")
|
||||||
|
/// .map(|mat| mat.pattern())
|
||||||
|
/// .collect();
|
||||||
|
/// assert_eq!(vec![PatternID::ZERO], matches);
|
||||||
|
/// # Some(()) }
|
||||||
|
/// # if cfg!(all(feature = "std", any(
|
||||||
|
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||||
|
/// # ))) {
|
||||||
|
/// # example().unwrap()
|
||||||
|
/// # } else {
|
||||||
|
/// # assert!(example().is_none());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Searcher {
|
||||||
|
patterns: Arc<Patterns>,
|
||||||
|
rabinkarp: RabinKarp,
|
||||||
|
search_kind: SearchKind,
|
||||||
|
minimum_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum SearchKind {
|
||||||
|
Teddy(teddy::Searcher),
|
||||||
|
RabinKarp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Searcher {
|
||||||
|
/// A convenience function for constructing a searcher from an iterator
|
||||||
|
/// of things that can be converted to a `&[u8]`.
|
||||||
|
///
|
||||||
|
/// If a searcher could not be constructed (either because of an
|
||||||
|
/// unsupported CPU or because there are too many patterns), then `None`
|
||||||
|
/// is returned.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Basic usage:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
|
||||||
|
///
|
||||||
|
/// # fn example() -> Option<()> {
|
||||||
|
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||||
|
/// let matches: Vec<PatternID> = searcher
|
||||||
|
/// .find_iter("foobar")
|
||||||
|
/// .map(|mat| mat.pattern())
|
||||||
|
/// .collect();
|
||||||
|
/// assert_eq!(vec![PatternID::ZERO], matches);
|
||||||
|
/// # Some(()) }
|
||||||
|
/// # if cfg!(all(feature = "std", any(
|
||||||
|
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||||
|
/// # ))) {
|
||||||
|
/// # example().unwrap()
|
||||||
|
/// # } else {
|
||||||
|
/// # assert!(example().is_none());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn new<I, P>(patterns: I) -> Option<Searcher>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = P>,
|
||||||
|
P: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
Builder::new().extend(patterns).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A convenience function for calling `Config::new()`.
|
||||||
|
///
|
||||||
|
/// This is useful for avoiding an additional import.
|
||||||
|
pub fn config() -> Config {
|
||||||
|
Config::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A convenience function for calling `Builder::new()`.
|
||||||
|
///
|
||||||
|
/// This is useful for avoiding an additional import.
|
||||||
|
pub fn builder() -> Builder {
|
||||||
|
Builder::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the first occurrence of any of the patterns in this searcher,
|
||||||
|
/// according to its match semantics, in the given haystack. The `Match`
|
||||||
|
/// returned will include the identifier of the pattern that matched, which
|
||||||
|
/// corresponds to the index of the pattern (starting from `0`) in which it
|
||||||
|
/// was added.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Basic usage:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
|
||||||
|
///
|
||||||
|
/// # fn example() -> Option<()> {
|
||||||
|
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||||
|
/// let mat = searcher.find("foobar")?;
|
||||||
|
/// assert_eq!(PatternID::ZERO, mat.pattern());
|
||||||
|
/// assert_eq!(0, mat.start());
|
||||||
|
/// assert_eq!(6, mat.end());
|
||||||
|
/// # Some(()) }
|
||||||
|
/// # if cfg!(all(feature = "std", any(
|
||||||
|
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||||
|
/// # ))) {
|
||||||
|
/// # example().unwrap()
|
||||||
|
/// # } else {
|
||||||
|
/// # assert!(example().is_none());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn find<B: AsRef<[u8]>>(&self, haystack: B) -> Option<Match> {
|
||||||
|
let haystack = haystack.as_ref();
|
||||||
|
self.find_in(haystack, Span::from(0..haystack.len()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the first occurrence of any of the patterns in this searcher,
|
||||||
|
/// according to its match semantics, in the given haystack starting from
|
||||||
|
/// the given position.
|
||||||
|
///
|
||||||
|
/// The `Match` returned will include the identifier of the pattern that
|
||||||
|
/// matched, which corresponds to the index of the pattern (starting from
|
||||||
|
/// `0`) in which it was added. The offsets in the `Match` will be relative
|
||||||
|
/// to the start of `haystack` (and not `at`).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// When `span` does not correspond to a valid range in `haystack`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Basic usage:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID, Span};
|
||||||
|
///
|
||||||
|
/// # fn example() -> Option<()> {
|
||||||
|
/// let haystack = "foofoobar";
|
||||||
|
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||||
|
/// let mat = searcher.find_in(haystack, Span::from(3..haystack.len()))?;
|
||||||
|
/// assert_eq!(PatternID::ZERO, mat.pattern());
|
||||||
|
/// assert_eq!(3, mat.start());
|
||||||
|
/// assert_eq!(9, mat.end());
|
||||||
|
/// # Some(()) }
|
||||||
|
/// # if cfg!(all(feature = "std", any(
|
||||||
|
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||||
|
/// # ))) {
|
||||||
|
/// # example().unwrap()
|
||||||
|
/// # } else {
|
||||||
|
/// # assert!(example().is_none());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn find_in<B: AsRef<[u8]>>(
|
||||||
|
&self,
|
||||||
|
haystack: B,
|
||||||
|
span: Span,
|
||||||
|
) -> Option<Match> {
|
||||||
|
let haystack = haystack.as_ref();
|
||||||
|
match self.search_kind {
|
||||||
|
SearchKind::Teddy(ref teddy) => {
|
||||||
|
if haystack[span].len() < teddy.minimum_len() {
|
||||||
|
return self.find_in_slow(haystack, span);
|
||||||
|
}
|
||||||
|
teddy.find(&haystack[..span.end], span.start)
|
||||||
|
}
|
||||||
|
SearchKind::RabinKarp => {
|
||||||
|
self.rabinkarp.find_at(&haystack[..span.end], span.start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an iterator of non-overlapping occurrences of the patterns in
|
||||||
|
/// this searcher, according to its match semantics, in the given haystack.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// Basic usage:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
|
||||||
|
///
|
||||||
|
/// # fn example() -> Option<()> {
|
||||||
|
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||||
|
/// let matches: Vec<PatternID> = searcher
|
||||||
|
/// .find_iter("foobar fooba foofoo")
|
||||||
|
/// .map(|mat| mat.pattern())
|
||||||
|
/// .collect();
|
||||||
|
/// assert_eq!(vec![
|
||||||
|
/// PatternID::must(0),
|
||||||
|
/// PatternID::must(1),
|
||||||
|
/// PatternID::must(1),
|
||||||
|
/// PatternID::must(1),
|
||||||
|
/// ], matches);
|
||||||
|
/// # Some(()) }
|
||||||
|
/// # if cfg!(all(feature = "std", any(
|
||||||
|
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||||
|
/// # ))) {
|
||||||
|
/// # example().unwrap()
|
||||||
|
/// # } else {
|
||||||
|
/// # assert!(example().is_none());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn find_iter<'a, 'b, B: ?Sized + AsRef<[u8]>>(
|
||||||
|
&'a self,
|
||||||
|
haystack: &'b B,
|
||||||
|
) -> FindIter<'a, 'b> {
|
||||||
|
let haystack = haystack.as_ref();
|
||||||
|
let span = Span::from(0..haystack.len());
|
||||||
|
FindIter { searcher: self, haystack, span }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the match kind used by this packed searcher.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Basic usage:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::packed::{MatchKind, Searcher};
|
||||||
|
///
|
||||||
|
/// # fn example() -> Option<()> {
|
||||||
|
/// let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||||
|
/// // leftmost-first is the default.
|
||||||
|
/// assert_eq!(&MatchKind::LeftmostFirst, searcher.match_kind());
|
||||||
|
/// # Some(()) }
|
||||||
|
/// # if cfg!(all(feature = "std", any(
|
||||||
|
/// # target_arch = "x86_64", target_arch = "aarch64",
|
||||||
|
/// # ))) {
|
||||||
|
/// # example().unwrap()
|
||||||
|
/// # } else {
|
||||||
|
/// # assert!(example().is_none());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn match_kind(&self) -> &MatchKind {
|
||||||
|
self.patterns.match_kind()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum length of a haystack that is required in order for
|
||||||
|
/// packed searching to be effective.
|
||||||
|
///
|
||||||
|
/// In some cases, the underlying packed searcher may not be able to search
|
||||||
|
/// very short haystacks. When that occurs, the implementation will defer
|
||||||
|
/// to a slower non-packed searcher (which is still generally faster than
|
||||||
|
/// Aho-Corasick for a small number of patterns). However, callers may
|
||||||
|
/// want to avoid ever using the slower variant, which one can do by
|
||||||
|
/// never passing a haystack shorter than the minimum length returned by
|
||||||
|
/// this method.
|
||||||
|
#[inline]
|
||||||
|
pub fn minimum_len(&self) -> usize {
|
||||||
|
self.minimum_len
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the approximate total amount of heap used by this searcher, in
|
||||||
|
/// units of bytes.
|
||||||
|
#[inline]
|
||||||
|
pub fn memory_usage(&self) -> usize {
|
||||||
|
self.patterns.memory_usage()
|
||||||
|
+ self.rabinkarp.memory_usage()
|
||||||
|
+ self.search_kind.memory_usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use a slow (non-packed) searcher.
|
||||||
|
///
|
||||||
|
/// This is useful when a packed searcher could be constructed, but could
|
||||||
|
/// not be used to search a specific haystack. For example, if Teddy was
|
||||||
|
/// built but the haystack is smaller than ~34 bytes, then Teddy might not
|
||||||
|
/// be able to run.
|
||||||
|
fn find_in_slow(&self, haystack: &[u8], span: Span) -> Option<Match> {
|
||||||
|
self.rabinkarp.find_at(&haystack[..span.end], span.start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearchKind {
|
||||||
|
fn memory_usage(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
SearchKind::Teddy(ref ted) => ted.memory_usage(),
|
||||||
|
SearchKind::RabinKarp => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over non-overlapping matches from a packed searcher.
|
||||||
|
///
|
||||||
|
/// The lifetime `'s` refers to the lifetime of the underlying [`Searcher`],
|
||||||
|
/// while the lifetime `'h` refers to the lifetime of the haystack being
|
||||||
|
/// searched.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FindIter<'s, 'h> {
|
||||||
|
searcher: &'s Searcher,
|
||||||
|
haystack: &'h [u8],
|
||||||
|
span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s, 'h> Iterator for FindIter<'s, 'h> {
|
||||||
|
type Item = Match;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Match> {
|
||||||
|
if self.span.start > self.span.end {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match self.searcher.find_in(self.haystack, self.span) {
|
||||||
|
None => None,
|
||||||
|
Some(m) => {
|
||||||
|
self.span.start = m.end();
|
||||||
|
Some(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
third_party/crates/aho-corasick-1.1.5/src/packed/ext.rs
vendored
Normal file
39
third_party/crates/aho-corasick-1.1.5/src/packed/ext.rs
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/// A trait for adding some helper routines to pointers.
|
||||||
|
pub(crate) trait Pointer {
|
||||||
|
/// Returns the distance, in units of `T`, between `self` and `origin`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Same as `ptr::offset_from` in addition to `self >= origin`.
|
||||||
|
unsafe fn distance(self, origin: Self) -> usize;
|
||||||
|
|
||||||
|
/// Casts this pointer to `usize`.
|
||||||
|
///
|
||||||
|
/// Callers should not convert the `usize` back to a pointer if at all
|
||||||
|
/// possible. (And if you believe it's necessary, open an issue to discuss
|
||||||
|
/// why. Otherwise, it has the potential to violate pointer provenance.)
|
||||||
|
/// The purpose of this function is just to be able to do arithmetic, i.e.,
|
||||||
|
/// computing offsets or alignments.
|
||||||
|
fn as_usize(self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Pointer for *const T {
|
||||||
|
unsafe fn distance(self, origin: *const T) -> usize {
|
||||||
|
// TODO: Replace with `ptr::sub_ptr` once stabilized.
|
||||||
|
usize::try_from(self.offset_from(origin)).unwrap_unchecked()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_usize(self) -> usize {
|
||||||
|
self as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Pointer for *mut T {
|
||||||
|
unsafe fn distance(self, origin: *mut T) -> usize {
|
||||||
|
(self as *const T).distance(origin as *const T)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_usize(self) -> usize {
|
||||||
|
(self as *const T).as_usize()
|
||||||
|
}
|
||||||
|
}
|
||||||
120
third_party/crates/aho-corasick-1.1.5/src/packed/mod.rs
vendored
Normal file
120
third_party/crates/aho-corasick-1.1.5/src/packed/mod.rs
vendored
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/*!
|
||||||
|
Provides packed multiple substring search, principally for a small number of
|
||||||
|
patterns.
|
||||||
|
|
||||||
|
This sub-module provides vectorized routines for quickly finding
|
||||||
|
matches of a small number of patterns. In general, users of this crate
|
||||||
|
shouldn't need to interface with this module directly, as the primary
|
||||||
|
[`AhoCorasick`](crate::AhoCorasick) searcher will use these routines
|
||||||
|
automatically as a prefilter when applicable. However, in some cases, callers
|
||||||
|
may want to bypass the Aho-Corasick machinery entirely and use this vectorized
|
||||||
|
searcher directly.
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
The primary types in this sub-module are:
|
||||||
|
|
||||||
|
* [`Searcher`] executes the actual search algorithm to report matches in a
|
||||||
|
haystack.
|
||||||
|
* [`Builder`] accumulates patterns incrementally and can construct a
|
||||||
|
`Searcher`.
|
||||||
|
* [`Config`] permits tuning the searcher, and itself will produce a `Builder`
|
||||||
|
(which can then be used to build a `Searcher`). Currently, the only tuneable
|
||||||
|
knob are the match semantics, but this may be expanded in the future.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
This example shows how to create a searcher from an iterator of patterns.
|
||||||
|
By default, leftmost-first match semantics are used. (See the top-level
|
||||||
|
[`MatchKind`] type for more details about match semantics, which apply
|
||||||
|
similarly to packed substring search.)
|
||||||
|
|
||||||
|
```
|
||||||
|
use aho_corasick::{packed::{MatchKind, Searcher}, PatternID};
|
||||||
|
|
||||||
|
# fn example() -> Option<()> {
|
||||||
|
let searcher = Searcher::new(["foobar", "foo"].iter().cloned())?;
|
||||||
|
let matches: Vec<PatternID> = searcher
|
||||||
|
.find_iter("foobar")
|
||||||
|
.map(|mat| mat.pattern())
|
||||||
|
.collect();
|
||||||
|
assert_eq!(vec![PatternID::ZERO], matches);
|
||||||
|
# Some(()) }
|
||||||
|
# if cfg!(all(feature = "std", any(
|
||||||
|
# target_arch = "x86_64", target_arch = "aarch64",
|
||||||
|
# ))) {
|
||||||
|
# example().unwrap()
|
||||||
|
# } else {
|
||||||
|
# assert!(example().is_none());
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
This example shows how to use [`Config`] to change the match semantics to
|
||||||
|
leftmost-longest:
|
||||||
|
|
||||||
|
```
|
||||||
|
use aho_corasick::{packed::{Config, MatchKind}, PatternID};
|
||||||
|
|
||||||
|
# fn example() -> Option<()> {
|
||||||
|
let searcher = Config::new()
|
||||||
|
.match_kind(MatchKind::LeftmostLongest)
|
||||||
|
.builder()
|
||||||
|
.add("foo")
|
||||||
|
.add("foobar")
|
||||||
|
.build()?;
|
||||||
|
let matches: Vec<PatternID> = searcher
|
||||||
|
.find_iter("foobar")
|
||||||
|
.map(|mat| mat.pattern())
|
||||||
|
.collect();
|
||||||
|
assert_eq!(vec![PatternID::must(1)], matches);
|
||||||
|
# Some(()) }
|
||||||
|
# if cfg!(all(feature = "std", any(
|
||||||
|
# target_arch = "x86_64", target_arch = "aarch64",
|
||||||
|
# ))) {
|
||||||
|
# example().unwrap()
|
||||||
|
# } else {
|
||||||
|
# assert!(example().is_none());
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
# Packed substring searching
|
||||||
|
|
||||||
|
Packed substring searching refers to the use of SIMD (Single Instruction,
|
||||||
|
Multiple Data) to accelerate the detection of matches in a haystack. Unlike
|
||||||
|
conventional algorithms, such as Aho-Corasick, SIMD algorithms for substring
|
||||||
|
search tend to do better with a small number of patterns, where as Aho-Corasick
|
||||||
|
generally maintains reasonably consistent performance regardless of the number
|
||||||
|
of patterns you give it. Because of this, the vectorized searcher in this
|
||||||
|
sub-module cannot be used as a general purpose searcher, since building the
|
||||||
|
searcher may fail even when given a small number of patterns. However, in
|
||||||
|
exchange, when searching for a small number of patterns, searching can be quite
|
||||||
|
a bit faster than Aho-Corasick (sometimes by an order of magnitude).
|
||||||
|
|
||||||
|
The key take away here is that constructing a searcher from a list of patterns
|
||||||
|
is a fallible operation with no clear rules for when it will fail. While the
|
||||||
|
precise conditions under which building a searcher can fail is specifically an
|
||||||
|
implementation detail, here are some common reasons:
|
||||||
|
|
||||||
|
* Too many patterns were given. Typically, the limit is on the order of 100 or
|
||||||
|
so, but this limit may fluctuate based on available CPU features.
|
||||||
|
* The available packed algorithms require CPU features that aren't available.
|
||||||
|
For example, currently, this crate only provides packed algorithms for
|
||||||
|
`x86_64` and `aarch64`. Therefore, constructing a packed searcher on any
|
||||||
|
other target will always fail.
|
||||||
|
* Zero patterns were given, or one of the patterns given was empty. Packed
|
||||||
|
searchers require at least one pattern and that all patterns are non-empty.
|
||||||
|
* Something else about the nature of the patterns (typically based on
|
||||||
|
heuristics) suggests that a packed searcher would perform very poorly, so
|
||||||
|
no searcher is built.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub use crate::packed::api::{Builder, Config, FindIter, MatchKind, Searcher};
|
||||||
|
|
||||||
|
mod api;
|
||||||
|
mod ext;
|
||||||
|
mod pattern;
|
||||||
|
mod rabinkarp;
|
||||||
|
mod teddy;
|
||||||
|
#[cfg(all(feature = "std", test))]
|
||||||
|
mod tests;
|
||||||
|
mod vector;
|
||||||
480
third_party/crates/aho-corasick-1.1.5/src/packed/pattern.rs
vendored
Normal file
480
third_party/crates/aho-corasick-1.1.5/src/packed/pattern.rs
vendored
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
use core::{cmp, fmt, mem, u16, usize};
|
||||||
|
|
||||||
|
use alloc::{boxed::Box, string::String, vec, vec::Vec};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
packed::{api::MatchKind, ext::Pointer},
|
||||||
|
PatternID,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A non-empty collection of non-empty patterns to search for.
|
||||||
|
///
|
||||||
|
/// This collection of patterns is what is passed around to both execute
|
||||||
|
/// searches and to construct the searchers themselves. Namely, this permits
|
||||||
|
/// searches to avoid copying all of the patterns, and allows us to keep only
|
||||||
|
/// one copy throughout all packed searchers.
|
||||||
|
///
|
||||||
|
/// Note that this collection is not a set. The same pattern can appear more
|
||||||
|
/// than once.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Patterns {
|
||||||
|
/// The match semantics supported by this collection of patterns.
|
||||||
|
///
|
||||||
|
/// The match semantics determines the order of the iterator over patterns.
|
||||||
|
/// For leftmost-first, patterns are provided in the same order as were
|
||||||
|
/// provided by the caller. For leftmost-longest, patterns are provided in
|
||||||
|
/// descending order of length, with ties broken by the order in which they
|
||||||
|
/// were provided by the caller.
|
||||||
|
kind: MatchKind,
|
||||||
|
/// The collection of patterns, indexed by their identifier.
|
||||||
|
by_id: Vec<Vec<u8>>,
|
||||||
|
/// The order of patterns defined for iteration, given by pattern
|
||||||
|
/// identifiers. The order of `by_id` and `order` is always the same for
|
||||||
|
/// leftmost-first semantics, but may be different for leftmost-longest
|
||||||
|
/// semantics.
|
||||||
|
order: Vec<PatternID>,
|
||||||
|
/// The length of the smallest pattern, in bytes.
|
||||||
|
minimum_len: usize,
|
||||||
|
/// The total number of pattern bytes across the entire collection. This
|
||||||
|
/// is used for reporting total heap usage in constant time.
|
||||||
|
total_pattern_bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// BREADCRUMBS: I think we want to experiment with a different bucket
|
||||||
|
// representation. Basically, each bucket is just a Range<usize> to a single
|
||||||
|
// contiguous allocation? Maybe length-prefixed patterns or something? The
|
||||||
|
// idea is to try to get rid of the pointer chasing in verification. I don't
|
||||||
|
// know that that is the issue, but I suspect it is.
|
||||||
|
|
||||||
|
impl Patterns {
|
||||||
|
/// Create a new collection of patterns for the given match semantics. The
|
||||||
|
/// ID of each pattern is the index of the pattern at which it occurs in
|
||||||
|
/// the `by_id` slice.
|
||||||
|
///
|
||||||
|
/// If any of the patterns in the slice given are empty, then this panics.
|
||||||
|
/// Similarly, if the number of patterns given is zero, then this also
|
||||||
|
/// panics.
|
||||||
|
pub(crate) fn new() -> Patterns {
|
||||||
|
Patterns {
|
||||||
|
kind: MatchKind::default(),
|
||||||
|
by_id: vec![],
|
||||||
|
order: vec![],
|
||||||
|
minimum_len: usize::MAX,
|
||||||
|
total_pattern_bytes: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a pattern to this collection.
|
||||||
|
///
|
||||||
|
/// This panics if the pattern given is empty.
|
||||||
|
pub(crate) fn add(&mut self, bytes: &[u8]) {
|
||||||
|
assert!(!bytes.is_empty());
|
||||||
|
assert!(self.by_id.len() <= u16::MAX as usize);
|
||||||
|
|
||||||
|
let id = PatternID::new(self.by_id.len()).unwrap();
|
||||||
|
self.order.push(id);
|
||||||
|
self.by_id.push(bytes.to_vec());
|
||||||
|
self.minimum_len = cmp::min(self.minimum_len, bytes.len());
|
||||||
|
self.total_pattern_bytes += bytes.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the match kind semantics for this collection of patterns.
|
||||||
|
///
|
||||||
|
/// If the kind is not set, then the default is leftmost-first.
|
||||||
|
pub(crate) fn set_match_kind(&mut self, kind: MatchKind) {
|
||||||
|
self.kind = kind;
|
||||||
|
match self.kind {
|
||||||
|
MatchKind::LeftmostFirst => {
|
||||||
|
self.order.sort();
|
||||||
|
}
|
||||||
|
MatchKind::LeftmostLongest => {
|
||||||
|
let (order, by_id) = (&mut self.order, &mut self.by_id);
|
||||||
|
order.sort_by(|&id1, &id2| {
|
||||||
|
by_id[id1].len().cmp(&by_id[id2].len()).reverse()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of patterns in this collection.
|
||||||
|
///
|
||||||
|
/// This is guaranteed to be greater than zero.
|
||||||
|
pub(crate) fn len(&self) -> usize {
|
||||||
|
self.by_id.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if this collection of patterns is empty.
|
||||||
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the approximate total amount of heap used by these patterns, in
|
||||||
|
/// units of bytes.
|
||||||
|
pub(crate) fn memory_usage(&self) -> usize {
|
||||||
|
self.order.len() * mem::size_of::<PatternID>()
|
||||||
|
+ self.by_id.len() * mem::size_of::<Vec<u8>>()
|
||||||
|
+ self.total_pattern_bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears all heap memory associated with this collection of patterns and
|
||||||
|
/// resets all state such that it is a valid empty collection.
|
||||||
|
pub(crate) fn reset(&mut self) {
|
||||||
|
self.kind = MatchKind::default();
|
||||||
|
self.by_id.clear();
|
||||||
|
self.order.clear();
|
||||||
|
self.minimum_len = usize::MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length, in bytes, of the smallest pattern.
|
||||||
|
///
|
||||||
|
/// This is guaranteed to be at least one.
|
||||||
|
pub(crate) fn minimum_len(&self) -> usize {
|
||||||
|
self.minimum_len
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the match semantics used by these patterns.
|
||||||
|
pub(crate) fn match_kind(&self) -> &MatchKind {
|
||||||
|
&self.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the pattern with the given identifier. If such a pattern does
|
||||||
|
/// not exist, then this panics.
|
||||||
|
pub(crate) fn get(&self, id: PatternID) -> Pattern<'_> {
|
||||||
|
Pattern(&self.by_id[id])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the pattern with the given identifier without performing bounds
|
||||||
|
/// checks.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Callers must ensure that a pattern with the given identifier exists
|
||||||
|
/// before using this method.
|
||||||
|
pub(crate) unsafe fn get_unchecked(&self, id: PatternID) -> Pattern<'_> {
|
||||||
|
Pattern(self.by_id.get_unchecked(id.as_usize()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an iterator over all the patterns in this collection, in the
|
||||||
|
/// order in which they should be matched.
|
||||||
|
///
|
||||||
|
/// Specifically, in a naive multi-pattern matcher, the following is
|
||||||
|
/// guaranteed to satisfy the match semantics of this collection of
|
||||||
|
/// patterns:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// for i in 0..haystack.len():
|
||||||
|
/// for p in patterns.iter():
|
||||||
|
/// if haystack[i..].starts_with(p.bytes()):
|
||||||
|
/// return Match(p.id(), i, i + p.bytes().len())
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Namely, among the patterns in a collection, if they are matched in
|
||||||
|
/// the order provided by this iterator, then the result is guaranteed
|
||||||
|
/// to satisfy the correct match semantics. (Either leftmost-first or
|
||||||
|
/// leftmost-longest.)
|
||||||
|
pub(crate) fn iter(&self) -> PatternIter<'_> {
|
||||||
|
PatternIter { patterns: self, i: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over the patterns in the `Patterns` collection.
|
||||||
|
///
|
||||||
|
/// The order of the patterns provided by this iterator is consistent with the
|
||||||
|
/// match semantics of the originating collection of patterns.
|
||||||
|
///
|
||||||
|
/// The lifetime `'p` corresponds to the lifetime of the collection of patterns
|
||||||
|
/// this is iterating over.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct PatternIter<'p> {
|
||||||
|
patterns: &'p Patterns,
|
||||||
|
i: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'p> Iterator for PatternIter<'p> {
|
||||||
|
type Item = (PatternID, Pattern<'p>);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(PatternID, Pattern<'p>)> {
|
||||||
|
if self.i >= self.patterns.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let id = self.patterns.order[self.i];
|
||||||
|
let p = self.patterns.get(id);
|
||||||
|
self.i += 1;
|
||||||
|
Some((id, p))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A pattern that is used in packed searching.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Pattern<'a>(&'a [u8]);
|
||||||
|
|
||||||
|
impl<'a> fmt::Debug for Pattern<'a> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Pattern")
|
||||||
|
.field("lit", &String::from_utf8_lossy(self.0))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'p> Pattern<'p> {
|
||||||
|
/// Returns the length of this pattern, in bytes.
|
||||||
|
pub(crate) fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the bytes of this pattern.
|
||||||
|
pub(crate) fn bytes(&self) -> &[u8] {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the first `len` low nybbles from this pattern. If this pattern
|
||||||
|
/// is shorter than `len`, then this panics.
|
||||||
|
pub(crate) fn low_nybbles(&self, len: usize) -> Box<[u8]> {
|
||||||
|
let mut nybs = vec![0; len].into_boxed_slice();
|
||||||
|
for (i, byte) in self.bytes().iter().take(len).enumerate() {
|
||||||
|
nybs[i] = byte & 0xF;
|
||||||
|
}
|
||||||
|
nybs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this pattern is a prefix of the given bytes.
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn is_prefix(&self, bytes: &[u8]) -> bool {
|
||||||
|
is_prefix(bytes, self.bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if this pattern is a prefix of the haystack given by the
|
||||||
|
/// raw `start` and `end` pointers.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// * It must be the case that `start < end` and that the distance between
|
||||||
|
/// them is at least equal to `V::BYTES`. That is, it must always be valid
|
||||||
|
/// to do at least an unaligned load of `V` at `start`.
|
||||||
|
/// * Both `start` and `end` must be valid for reads.
|
||||||
|
/// * Both `start` and `end` must point to an initialized value.
|
||||||
|
/// * Both `start` and `end` must point to the same allocated object and
|
||||||
|
/// must either be in bounds or at most one byte past the end of the
|
||||||
|
/// allocated object.
|
||||||
|
/// * Both `start` and `end` must be _derived from_ a pointer to the same
|
||||||
|
/// object.
|
||||||
|
/// * The distance between `start` and `end` must not overflow `isize`.
|
||||||
|
/// * The distance being in bounds must not rely on "wrapping around" the
|
||||||
|
/// address space.
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) unsafe fn is_prefix_raw(
|
||||||
|
&self,
|
||||||
|
start: *const u8,
|
||||||
|
end: *const u8,
|
||||||
|
) -> bool {
|
||||||
|
let patlen = self.bytes().len();
|
||||||
|
let haylen = end.distance(start);
|
||||||
|
if patlen > haylen {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// SAFETY: We've checked that the haystack has length at least equal
|
||||||
|
// to this pattern. All other safety concerns are the responsibility
|
||||||
|
// of the caller.
|
||||||
|
is_equal_raw(start, self.bytes().as_ptr(), patlen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if `needle` is a prefix of `haystack`.
|
||||||
|
///
|
||||||
|
/// This uses a latency optimized variant of `memcmp` internally which *might*
|
||||||
|
/// make this faster for very short strings.
|
||||||
|
///
|
||||||
|
/// # Inlining
|
||||||
|
///
|
||||||
|
/// This routine is marked `inline(always)`. If you want to call this function
|
||||||
|
/// in a way that is not always inlined, you'll need to wrap a call to it in
|
||||||
|
/// another function that is marked as `inline(never)` or just `inline`.
|
||||||
|
#[inline(always)]
|
||||||
|
fn is_prefix(haystack: &[u8], needle: &[u8]) -> bool {
|
||||||
|
if needle.len() > haystack.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// SAFETY: Our pointers are derived directly from borrowed slices which
|
||||||
|
// uphold all of our safety guarantees except for length. We account for
|
||||||
|
// length with the check above.
|
||||||
|
unsafe { is_equal_raw(haystack.as_ptr(), needle.as_ptr(), needle.len()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare corresponding bytes in `x` and `y` for equality.
|
||||||
|
///
|
||||||
|
/// That is, this returns true if and only if `x.len() == y.len()` and
|
||||||
|
/// `x[i] == y[i]` for all `0 <= i < x.len()`.
|
||||||
|
///
|
||||||
|
/// Note that this isn't used. We only use it in tests as a convenient way
|
||||||
|
/// of testing `is_equal_raw`.
|
||||||
|
///
|
||||||
|
/// # Inlining
|
||||||
|
///
|
||||||
|
/// This routine is marked `inline(always)`. If you want to call this function
|
||||||
|
/// in a way that is not always inlined, you'll need to wrap a call to it in
|
||||||
|
/// another function that is marked as `inline(never)` or just `inline`.
|
||||||
|
///
|
||||||
|
/// # Motivation
|
||||||
|
///
|
||||||
|
/// Why not use slice equality instead? Well, slice equality usually results in
|
||||||
|
/// a call out to the current platform's `libc` which might not be inlineable
|
||||||
|
/// or have other overhead. This routine isn't guaranteed to be a win, but it
|
||||||
|
/// might be in some cases.
|
||||||
|
#[cfg(test)]
|
||||||
|
#[inline(always)]
|
||||||
|
fn is_equal(x: &[u8], y: &[u8]) -> bool {
|
||||||
|
if x.len() != y.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// SAFETY: Our pointers are derived directly from borrowed slices which
|
||||||
|
// uphold all of our safety guarantees except for length. We account for
|
||||||
|
// length with the check above.
|
||||||
|
unsafe { is_equal_raw(x.as_ptr(), y.as_ptr(), x.len()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare `n` bytes at the given pointers for equality.
|
||||||
|
///
|
||||||
|
/// This returns true if and only if `*x.add(i) == *y.add(i)` for all
|
||||||
|
/// `0 <= i < n`.
|
||||||
|
///
|
||||||
|
/// # Inlining
|
||||||
|
///
|
||||||
|
/// This routine is marked `inline(always)`. If you want to call this function
|
||||||
|
/// in a way that is not always inlined, you'll need to wrap a call to it in
|
||||||
|
/// another function that is marked as `inline(never)` or just `inline`.
|
||||||
|
///
|
||||||
|
/// # Motivation
|
||||||
|
///
|
||||||
|
/// Why not use slice equality instead? Well, slice equality usually results in
|
||||||
|
/// a call out to the current platform's `libc` which might not be inlineable
|
||||||
|
/// or have other overhead. This routine isn't guaranteed to be a win, but it
|
||||||
|
/// might be in some cases.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// * Both `x` and `y` must be valid for reads of up to `n` bytes.
|
||||||
|
/// * Both `x` and `y` must point to an initialized value.
|
||||||
|
/// * Both `x` and `y` must each point to an allocated object and
|
||||||
|
/// must either be in bounds or at most one byte past the end of the
|
||||||
|
/// allocated object. `x` and `y` do not need to point to the same allocated
|
||||||
|
/// object, but they may.
|
||||||
|
/// * Both `x` and `y` must be _derived from_ a pointer to their respective
|
||||||
|
/// allocated objects.
|
||||||
|
/// * The distance between `x` and `x+n` must not overflow `isize`. Similarly
|
||||||
|
/// for `y` and `y+n`.
|
||||||
|
/// * The distance being in bounds must not rely on "wrapping around" the
|
||||||
|
/// address space.
|
||||||
|
#[inline(always)]
|
||||||
|
unsafe fn is_equal_raw(mut x: *const u8, mut y: *const u8, n: usize) -> bool {
|
||||||
|
// If we don't have enough bytes to do 4-byte at a time loads, then
|
||||||
|
// handle each possible length specially. Note that I used to have a
|
||||||
|
// byte-at-a-time loop here and that turned out to be quite a bit slower
|
||||||
|
// for the memmem/pathological/defeat-simple-vector-alphabet benchmark.
|
||||||
|
if n < 4 {
|
||||||
|
return match n {
|
||||||
|
0 => true,
|
||||||
|
1 => x.read() == y.read(),
|
||||||
|
2 => {
|
||||||
|
x.cast::<u16>().read_unaligned()
|
||||||
|
== y.cast::<u16>().read_unaligned()
|
||||||
|
}
|
||||||
|
// I also tried copy_nonoverlapping here and it looks like the
|
||||||
|
// codegen is the same.
|
||||||
|
3 => x.cast::<[u8; 3]>().read() == y.cast::<[u8; 3]>().read(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// When we have 4 or more bytes to compare, then proceed in chunks of 4 at
|
||||||
|
// a time using unaligned loads.
|
||||||
|
//
|
||||||
|
// Also, why do 4 byte loads instead of, say, 8 byte loads? The reason is
|
||||||
|
// that this particular version of memcmp is likely to be called with tiny
|
||||||
|
// needles. That means that if we do 8 byte loads, then a higher proportion
|
||||||
|
// of memcmp calls will use the slower variant above. With that said, this
|
||||||
|
// is a hypothesis and is only loosely supported by benchmarks. There's
|
||||||
|
// likely some improvement that could be made here. The main thing here
|
||||||
|
// though is to optimize for latency, not throughput.
|
||||||
|
|
||||||
|
// SAFETY: The caller is responsible for ensuring the pointers we get are
|
||||||
|
// valid and readable for at least `n` bytes. We also do unaligned loads,
|
||||||
|
// so there's no need to ensure we're aligned. (This is justified by this
|
||||||
|
// routine being specifically for short strings.)
|
||||||
|
let xend = x.add(n.wrapping_sub(4));
|
||||||
|
let yend = y.add(n.wrapping_sub(4));
|
||||||
|
while x < xend {
|
||||||
|
let vx = x.cast::<u32>().read_unaligned();
|
||||||
|
let vy = y.cast::<u32>().read_unaligned();
|
||||||
|
if vx != vy {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
x = x.add(4);
|
||||||
|
y = y.add(4);
|
||||||
|
}
|
||||||
|
let vx = xend.cast::<u32>().read_unaligned();
|
||||||
|
let vy = yend.cast::<u32>().read_unaligned();
|
||||||
|
vx == vy
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn equals_different_lengths() {
|
||||||
|
assert!(!is_equal(b"", b"a"));
|
||||||
|
assert!(!is_equal(b"a", b""));
|
||||||
|
assert!(!is_equal(b"ab", b"a"));
|
||||||
|
assert!(!is_equal(b"a", b"ab"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn equals_mismatch() {
|
||||||
|
let one_mismatch = [
|
||||||
|
(&b"a"[..], &b"x"[..]),
|
||||||
|
(&b"ab"[..], &b"ax"[..]),
|
||||||
|
(&b"abc"[..], &b"abx"[..]),
|
||||||
|
(&b"abcd"[..], &b"abcx"[..]),
|
||||||
|
(&b"abcde"[..], &b"abcdx"[..]),
|
||||||
|
(&b"abcdef"[..], &b"abcdex"[..]),
|
||||||
|
(&b"abcdefg"[..], &b"abcdefx"[..]),
|
||||||
|
(&b"abcdefgh"[..], &b"abcdefgx"[..]),
|
||||||
|
(&b"abcdefghi"[..], &b"abcdefghx"[..]),
|
||||||
|
(&b"abcdefghij"[..], &b"abcdefghix"[..]),
|
||||||
|
(&b"abcdefghijk"[..], &b"abcdefghijx"[..]),
|
||||||
|
(&b"abcdefghijkl"[..], &b"abcdefghijkx"[..]),
|
||||||
|
(&b"abcdefghijklm"[..], &b"abcdefghijklx"[..]),
|
||||||
|
(&b"abcdefghijklmn"[..], &b"abcdefghijklmx"[..]),
|
||||||
|
];
|
||||||
|
for (x, y) in one_mismatch {
|
||||||
|
assert_eq!(x.len(), y.len(), "lengths should match");
|
||||||
|
assert!(!is_equal(x, y));
|
||||||
|
assert!(!is_equal(y, x));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn equals_yes() {
|
||||||
|
assert!(is_equal(b"", b""));
|
||||||
|
assert!(is_equal(b"a", b"a"));
|
||||||
|
assert!(is_equal(b"ab", b"ab"));
|
||||||
|
assert!(is_equal(b"abc", b"abc"));
|
||||||
|
assert!(is_equal(b"abcd", b"abcd"));
|
||||||
|
assert!(is_equal(b"abcde", b"abcde"));
|
||||||
|
assert!(is_equal(b"abcdef", b"abcdef"));
|
||||||
|
assert!(is_equal(b"abcdefg", b"abcdefg"));
|
||||||
|
assert!(is_equal(b"abcdefgh", b"abcdefgh"));
|
||||||
|
assert!(is_equal(b"abcdefghi", b"abcdefghi"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefix() {
|
||||||
|
assert!(is_prefix(b"", b""));
|
||||||
|
assert!(is_prefix(b"a", b""));
|
||||||
|
assert!(is_prefix(b"ab", b""));
|
||||||
|
assert!(is_prefix(b"foo", b"foo"));
|
||||||
|
assert!(is_prefix(b"foobar", b"foo"));
|
||||||
|
|
||||||
|
assert!(!is_prefix(b"foo", b"fob"));
|
||||||
|
assert!(!is_prefix(b"foobar", b"fob"));
|
||||||
|
}
|
||||||
|
}
|
||||||
168
third_party/crates/aho-corasick-1.1.5/src/packed/rabinkarp.rs
vendored
Normal file
168
third_party/crates/aho-corasick-1.1.5/src/packed/rabinkarp.rs
vendored
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
use alloc::{sync::Arc, vec, vec::Vec};
|
||||||
|
|
||||||
|
use crate::{packed::pattern::Patterns, util::search::Match, PatternID};
|
||||||
|
|
||||||
|
/// The type of the rolling hash used in the Rabin-Karp algorithm.
|
||||||
|
type Hash = usize;
|
||||||
|
|
||||||
|
/// The number of buckets to store our patterns in. We don't want this to be
|
||||||
|
/// too big in order to avoid wasting memory, but we don't want it to be too
|
||||||
|
/// small either to avoid spending too much time confirming literals.
|
||||||
|
///
|
||||||
|
/// The number of buckets MUST be a power of two. Otherwise, determining the
|
||||||
|
/// bucket from a hash will slow down the code considerably. Using a power
|
||||||
|
/// of two means `hash % NUM_BUCKETS` can compile down to a simple `and`
|
||||||
|
/// instruction.
|
||||||
|
const NUM_BUCKETS: usize = 64;
|
||||||
|
|
||||||
|
/// An implementation of the Rabin-Karp algorithm. The main idea of this
|
||||||
|
/// algorithm is to maintain a rolling hash as it moves through the input, and
|
||||||
|
/// then check whether that hash corresponds to the same hash for any of the
|
||||||
|
/// patterns we're looking for.
|
||||||
|
///
|
||||||
|
/// A draw back of naively scaling Rabin-Karp to multiple patterns is that
|
||||||
|
/// it requires all of the patterns to be the same length, which in turn
|
||||||
|
/// corresponds to the number of bytes to hash. We adapt this to work for
|
||||||
|
/// multiple patterns of varying size by fixing the number of bytes to hash
|
||||||
|
/// to be the length of the smallest pattern. We also split the patterns into
|
||||||
|
/// several buckets to hopefully make the confirmation step faster.
|
||||||
|
///
|
||||||
|
/// Wikipedia has a decent explanation, if a bit heavy on the theory:
|
||||||
|
/// https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm
|
||||||
|
///
|
||||||
|
/// But ESMAJ provides something a bit more concrete:
|
||||||
|
/// https://www-igm.univ-mlv.fr/~lecroq/string/node5.html
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct RabinKarp {
|
||||||
|
/// The patterns we're searching for.
|
||||||
|
patterns: Arc<Patterns>,
|
||||||
|
/// The order of patterns in each bucket is significant. Namely, they are
|
||||||
|
/// arranged such that the first one to match is the correct match. This
|
||||||
|
/// may not necessarily correspond to the order provided by the caller.
|
||||||
|
/// For example, if leftmost-longest semantics are used, then the patterns
|
||||||
|
/// are sorted by their length in descending order. If leftmost-first
|
||||||
|
/// semantics are used, then the patterns are sorted by their pattern ID
|
||||||
|
/// in ascending order (which corresponds to the caller's order).
|
||||||
|
buckets: Vec<Vec<(Hash, PatternID)>>,
|
||||||
|
/// The length of the hashing window. Generally, this corresponds to the
|
||||||
|
/// length of the smallest pattern.
|
||||||
|
hash_len: usize,
|
||||||
|
/// The factor to subtract out of a hash before updating it with a new
|
||||||
|
/// byte.
|
||||||
|
hash_2pow: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RabinKarp {
|
||||||
|
/// Compile a new Rabin-Karp matcher from the patterns given.
|
||||||
|
///
|
||||||
|
/// This panics if any of the patterns in the collection are empty, or if
|
||||||
|
/// the collection is itself empty.
|
||||||
|
pub(crate) fn new(patterns: &Arc<Patterns>) -> RabinKarp {
|
||||||
|
assert!(patterns.len() >= 1);
|
||||||
|
let hash_len = patterns.minimum_len();
|
||||||
|
assert!(hash_len >= 1);
|
||||||
|
|
||||||
|
let mut hash_2pow = 1usize;
|
||||||
|
for _ in 1..hash_len {
|
||||||
|
hash_2pow = hash_2pow.wrapping_shl(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rk = RabinKarp {
|
||||||
|
patterns: Arc::clone(patterns),
|
||||||
|
buckets: vec![vec![]; NUM_BUCKETS],
|
||||||
|
hash_len,
|
||||||
|
hash_2pow,
|
||||||
|
};
|
||||||
|
for (id, pat) in patterns.iter() {
|
||||||
|
let hash = rk.hash(&pat.bytes()[..rk.hash_len]);
|
||||||
|
let bucket = hash % NUM_BUCKETS;
|
||||||
|
rk.buckets[bucket].push((hash, id));
|
||||||
|
}
|
||||||
|
rk
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the first matching pattern in the given haystack, begining the
|
||||||
|
/// search at `at`.
|
||||||
|
pub(crate) fn find_at(
|
||||||
|
&self,
|
||||||
|
haystack: &[u8],
|
||||||
|
mut at: usize,
|
||||||
|
) -> Option<Match> {
|
||||||
|
assert_eq!(NUM_BUCKETS, self.buckets.len());
|
||||||
|
|
||||||
|
if at + self.hash_len > haystack.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut hash = self.hash(&haystack[at..at + self.hash_len]);
|
||||||
|
loop {
|
||||||
|
let bucket = &self.buckets[hash % NUM_BUCKETS];
|
||||||
|
for &(phash, pid) in bucket {
|
||||||
|
if phash == hash {
|
||||||
|
if let Some(c) = self.verify(pid, haystack, at) {
|
||||||
|
return Some(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if at + self.hash_len >= haystack.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
hash = self.update_hash(
|
||||||
|
hash,
|
||||||
|
haystack[at],
|
||||||
|
haystack[at + self.hash_len],
|
||||||
|
);
|
||||||
|
at += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the approximate total amount of heap used by this searcher, in
|
||||||
|
/// units of bytes.
|
||||||
|
pub(crate) fn memory_usage(&self) -> usize {
|
||||||
|
self.buckets.len() * core::mem::size_of::<Vec<(Hash, PatternID)>>()
|
||||||
|
+ self.patterns.len() * core::mem::size_of::<(Hash, PatternID)>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify whether the pattern with the given id matches at
|
||||||
|
/// `haystack[at..]`.
|
||||||
|
///
|
||||||
|
/// We tag this function as `cold` because it helps improve codegen.
|
||||||
|
/// Intuitively, it would seem like inlining it would be better. However,
|
||||||
|
/// the only time this is called and a match is not found is when there
|
||||||
|
/// there is a hash collision, or when a prefix of a pattern matches but
|
||||||
|
/// the entire pattern doesn't match. This is hopefully fairly rare, and
|
||||||
|
/// if it does occur a lot, it's going to be slow no matter what we do.
|
||||||
|
#[cold]
|
||||||
|
fn verify(
|
||||||
|
&self,
|
||||||
|
id: PatternID,
|
||||||
|
haystack: &[u8],
|
||||||
|
at: usize,
|
||||||
|
) -> Option<Match> {
|
||||||
|
let pat = self.patterns.get(id);
|
||||||
|
if pat.is_prefix(&haystack[at..]) {
|
||||||
|
Some(Match::new(id, at..at + pat.len()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hash the given bytes.
|
||||||
|
fn hash(&self, bytes: &[u8]) -> Hash {
|
||||||
|
assert_eq!(self.hash_len, bytes.len());
|
||||||
|
|
||||||
|
let mut hash = 0usize;
|
||||||
|
for &b in bytes {
|
||||||
|
hash = hash.wrapping_shl(1).wrapping_add(b as usize);
|
||||||
|
}
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the hash given based on removing `old_byte` at the beginning
|
||||||
|
/// of some byte string, and appending `new_byte` to the end of that same
|
||||||
|
/// byte string.
|
||||||
|
fn update_hash(&self, prev: Hash, old_byte: u8, new_byte: u8) -> Hash {
|
||||||
|
prev.wrapping_sub((old_byte as usize).wrapping_mul(self.hash_2pow))
|
||||||
|
.wrapping_shl(1)
|
||||||
|
.wrapping_add(new_byte as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
386
third_party/crates/aho-corasick-1.1.5/src/packed/teddy/README.md
vendored
Normal file
386
third_party/crates/aho-corasick-1.1.5/src/packed/teddy/README.md
vendored
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
Teddy is a SIMD accelerated multiple substring matching algorithm. The name
|
||||||
|
and the core ideas in the algorithm were learned from the [Hyperscan][1_u]
|
||||||
|
project. The implementation in this repository was mostly motivated for use in
|
||||||
|
accelerating regex searches by searching for small sets of required literals
|
||||||
|
extracted from the regex.
|
||||||
|
|
||||||
|
|
||||||
|
# Background
|
||||||
|
|
||||||
|
The key idea of Teddy is to do *packed* substring matching. In the literature,
|
||||||
|
packed substring matching is the idea of examining multiple bytes in a haystack
|
||||||
|
at a time to detect matches. Implementations of, for example, memchr (which
|
||||||
|
detects matches of a single byte) have been doing this for years. Only
|
||||||
|
recently, with the introduction of various SIMD instructions, has this been
|
||||||
|
extended to substring matching. The PCMPESTRI instruction (and its relatives),
|
||||||
|
for example, implements substring matching in hardware. It is, however, limited
|
||||||
|
to substrings of length 16 bytes or fewer, but this restriction is fine in a
|
||||||
|
regex engine, since we rarely care about the performance difference between
|
||||||
|
searching for a 16 byte literal and a 16 + N literal; 16 is already long
|
||||||
|
enough. The key downside of the PCMPESTRI instruction, on current (2016) CPUs
|
||||||
|
at least, is its latency and throughput. As a result, it is often faster to
|
||||||
|
do substring search with a Boyer-Moore (or Two-Way) variant and a well placed
|
||||||
|
memchr to quickly skip through the haystack.
|
||||||
|
|
||||||
|
There are fewer results from the literature on packed substring matching,
|
||||||
|
and even fewer for packed multiple substring matching. Ben-Kiki et al. [2]
|
||||||
|
describes use of PCMPESTRI for substring matching, but is mostly theoretical
|
||||||
|
and hand-waves performance. There is other theoretical work done by Bille [3]
|
||||||
|
as well.
|
||||||
|
|
||||||
|
The rest of the work in the field, as far as I'm aware, is by Faro and Kulekci
|
||||||
|
and is generally focused on multiple pattern search. Their first paper [4a]
|
||||||
|
introduces the concept of a fingerprint, which is computed for every block of
|
||||||
|
N bytes in every pattern. The haystack is then scanned N bytes at a time and
|
||||||
|
a fingerprint is computed in the same way it was computed for blocks in the
|
||||||
|
patterns. If the fingerprint corresponds to one that was found in a pattern,
|
||||||
|
then a verification step follows to confirm that one of the substrings with the
|
||||||
|
corresponding fingerprint actually matches at the current location. Various
|
||||||
|
implementation tricks are employed to make sure the fingerprint lookup is fast;
|
||||||
|
typically by truncating the fingerprint. (This may, of course, provoke more
|
||||||
|
steps in the verification process, so a balance must be struck.)
|
||||||
|
|
||||||
|
The main downside of [4a] is that the minimum substring length is 32 bytes,
|
||||||
|
presumably because of how the algorithm uses certain SIMD instructions. This
|
||||||
|
essentially makes it useless for general purpose regex matching, where a small
|
||||||
|
number of short patterns is far more likely.
|
||||||
|
|
||||||
|
Faro and Kulekci published another paper [4b] that is conceptually very similar
|
||||||
|
to [4a]. The key difference is that it uses the CRC32 instruction (introduced
|
||||||
|
as part of SSE 4.2) to compute fingerprint values. This also enables the
|
||||||
|
algorithm to work effectively on substrings as short as 7 bytes with 4 byte
|
||||||
|
windows. 7 bytes is unfortunately still too long. The window could be
|
||||||
|
technically shrunk to 2 bytes, thereby reducing minimum length to 3, but the
|
||||||
|
small window size ends up negating most performance benefits—and it's likely
|
||||||
|
the common case in a general purpose regex engine.
|
||||||
|
|
||||||
|
Faro and Kulekci also published [4c] that appears to be intended as a
|
||||||
|
replacement to using PCMPESTRI. In particular, it is specifically motivated by
|
||||||
|
the high throughput/latency time of PCMPESTRI and therefore chooses other SIMD
|
||||||
|
instructions that are faster. While this approach works for short substrings,
|
||||||
|
I personally couldn't see a way to generalize it to multiple substring search.
|
||||||
|
|
||||||
|
Faro and Kulekci have another paper [4d] that I haven't been able to read
|
||||||
|
because it is behind a paywall.
|
||||||
|
|
||||||
|
|
||||||
|
# Teddy
|
||||||
|
|
||||||
|
Finally, we get to Teddy. If the above literature review is complete, then it
|
||||||
|
appears that Teddy is a novel algorithm. More than that, in my experience, it
|
||||||
|
completely blows away the competition for short substrings, which is exactly
|
||||||
|
what we want in a general purpose regex engine. Again, the algorithm appears
|
||||||
|
to be developed by the authors of [Hyperscan][1_u]. Hyperscan was open sourced
|
||||||
|
late 2015, and no earlier history could be found. Therefore, tracking the exact
|
||||||
|
provenance of the algorithm with respect to the published literature seems
|
||||||
|
difficult.
|
||||||
|
|
||||||
|
At a high level, Teddy works somewhat similarly to the fingerprint algorithms
|
||||||
|
published by Faro and Kulekci, but Teddy does it in a way that scales a bit
|
||||||
|
better. Namely:
|
||||||
|
|
||||||
|
1. Teddy's core algorithm scans the haystack in 16 (for SSE, or 32 for AVX)
|
||||||
|
byte chunks. 16 (or 32) is significant because it corresponds to the number
|
||||||
|
of bytes in a SIMD vector.
|
||||||
|
2. Bitwise operations are performed on each chunk to discover if any region of
|
||||||
|
it matches a set of precomputed fingerprints from the patterns. If there are
|
||||||
|
matches, then a verification step is performed. In this implementation, our
|
||||||
|
verification step is naive. This can be improved upon.
|
||||||
|
|
||||||
|
The details to make this work are quite clever. First, we must choose how to
|
||||||
|
pick our fingerprints. In Hyperscan's implementation, I *believe* they use the
|
||||||
|
last N bytes of each substring, where N must be at least the minimum length of
|
||||||
|
any substring in the set being searched. In this implementation, we use the
|
||||||
|
first N bytes of each substring. (The tradeoffs between these choices aren't
|
||||||
|
yet clear to me.) We then must figure out how to quickly test whether an
|
||||||
|
occurrence of any fingerprint from the set of patterns appears in a 16 byte
|
||||||
|
block from the haystack. To keep things simple, let's assume N = 1 and examine
|
||||||
|
some examples to motivate the approach. Here are our patterns:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
baz
|
||||||
|
```
|
||||||
|
|
||||||
|
The corresponding fingerprints, for N = 1, are `f`, `b` and `b`. Now let's set
|
||||||
|
our 16 byte block to:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
bat cat foo bump
|
||||||
|
xxxxxxxxxxxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
To cut to the chase, Teddy works by using bitsets. In particular, Teddy creates
|
||||||
|
a mask that allows us to quickly compute membership of a fingerprint in a 16
|
||||||
|
byte block that also tells which pattern the fingerprint corresponds to. In
|
||||||
|
this case, our fingerprint is a single byte, so an appropriate abstraction is
|
||||||
|
a map from a single byte to a list of patterns that contain that fingerprint:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
f |--> foo
|
||||||
|
b |--> bar, baz
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, all we need to do is figure out how to represent this map in vector space
|
||||||
|
and use normal SIMD operations to perform a lookup. The first simplification
|
||||||
|
we can make is to represent our patterns as bit fields occupying a single
|
||||||
|
byte. This is important, because a single SIMD vector can store 16 bytes.
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
f |--> 00000001
|
||||||
|
b |--> 00000010, 00000100
|
||||||
|
```
|
||||||
|
|
||||||
|
How do we perform lookup though? It turns out that SSSE3 introduced a very cool
|
||||||
|
instruction called PSHUFB. The instruction takes two SIMD vectors, `A` and `B`,
|
||||||
|
and returns a third vector `C`. All vectors are treated as 16 8-bit integers.
|
||||||
|
`C` is formed by `C[i] = A[B[i]]`. (This is a bit of a simplification, but true
|
||||||
|
for the purposes of this algorithm. For full details, see [Intel's Intrinsics
|
||||||
|
Guide][5_u].) This essentially lets us use the values in `B` to lookup values
|
||||||
|
in `A`.
|
||||||
|
|
||||||
|
If we could somehow cause `B` to contain our 16 byte block from the haystack,
|
||||||
|
and if `A` could contain our bitmasks, then we'd end up with something like
|
||||||
|
this for `A`:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
0x00 0x01 ... 0x62 ... 0x66 ... 0xFF
|
||||||
|
A = 0 0 00000110 00000001 0
|
||||||
|
```
|
||||||
|
|
||||||
|
And if `B` contains our window from our haystack, we could use shuffle to take
|
||||||
|
the values from `B` and use them to look up our bitsets in `A`. But of course,
|
||||||
|
we can't do this because `A` in the above example contains 256 bytes, which
|
||||||
|
is much larger than the size of a SIMD vector.
|
||||||
|
|
||||||
|
Nybbles to the rescue! A nybble is 4 bits. Instead of one mask to hold all of
|
||||||
|
our bitsets, we can use two masks, where one mask corresponds to the lower four
|
||||||
|
bits of our fingerprint and the other mask corresponds to the upper four bits.
|
||||||
|
So our map now looks like:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
'f' & 0xF = 0x6 |--> 00000001
|
||||||
|
'f' >> 4 = 0x6 |--> 00000111
|
||||||
|
'b' & 0xF = 0x2 |--> 00000110
|
||||||
|
'b' >> 4 = 0x6 |--> 00000111
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that the bitsets for each nybble correspond to the union of all
|
||||||
|
fingerprints that contain that nybble. For example, both `f` and `b` have the
|
||||||
|
same upper 4 bits but differ on the lower 4 bits. Putting this together, we
|
||||||
|
have `A0`, `A1` and `B`, where `A0` is our mask for the lower nybble, `A1` is
|
||||||
|
our mask for the upper nybble and `B` is our 16 byte block from the haystack:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
0x00 0x01 0x02 0x03 ... 0x06 ... 0xF
|
||||||
|
A0 = 0 0 00000110 0 00000001 0
|
||||||
|
A1 = 0 0 0 0 00000111 0
|
||||||
|
B = b a t _ t p
|
||||||
|
B = 0x62 0x61 0x74 0x20 0x74 0x70
|
||||||
|
```
|
||||||
|
|
||||||
|
But of course, we can't use `B` with `PSHUFB` yet, since its values are 8 bits,
|
||||||
|
and we need indexes that are at most 4 bits (corresponding to one of 16
|
||||||
|
values). We can apply the same transformation to split `B` into lower and upper
|
||||||
|
nybbles as we did `A`. As before, `B0` corresponds to the lower nybbles and
|
||||||
|
`B1` corresponds to the upper nybbles:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
b a t _ c a t _ f o o _ b u m p
|
||||||
|
B0 = 0x2 0x1 0x4 0x0 0x3 0x1 0x4 0x0 0x6 0xF 0xF 0x0 0x2 0x5 0xD 0x0
|
||||||
|
B1 = 0x6 0x6 0x7 0x2 0x6 0x6 0x7 0x2 0x6 0x6 0x6 0x2 0x6 0x7 0x6 0x7
|
||||||
|
```
|
||||||
|
|
||||||
|
And now we have a nice correspondence. `B0` can index `A0` and `B1` can index
|
||||||
|
`A1`. Here's what we get when we apply `C0 = PSHUFB(A0, B0)`:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
b a ... f o ... p
|
||||||
|
A0[0x2] A0[0x1] A0[0x6] A0[0xF] A0[0x0]
|
||||||
|
C0 = 00000110 0 00000001 0 0
|
||||||
|
```
|
||||||
|
|
||||||
|
And `C1 = PSHUFB(A1, B1)`:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
b a ... f o ... p
|
||||||
|
A1[0x6] A1[0x6] A1[0x6] A1[0x6] A1[0x7]
|
||||||
|
C1 = 00000111 00000111 00000111 00000111 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice how neither one of `C0` or `C1` is guaranteed to report fully correct
|
||||||
|
results all on its own. For example, `C1` claims that `b` is a fingerprint for
|
||||||
|
the pattern `foo` (since `A1[0x6] = 00000111`), and that `o` is a fingerprint
|
||||||
|
for all of our patterns. But if we combined `C0` and `C1` with an `AND`
|
||||||
|
operation:
|
||||||
|
|
||||||
|
```ignore
|
||||||
|
b a ... f o ... p
|
||||||
|
C = 00000110 0 00000001 0 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Then we now have that `C[i]` contains a bitset corresponding to the matching
|
||||||
|
fingerprints in a haystack's 16 byte block, where `i` is the `ith` byte in that
|
||||||
|
block.
|
||||||
|
|
||||||
|
Once we have that, we can look for the position of the least significant bit
|
||||||
|
in `C`. (Least significant because we only target little endian here. Thus,
|
||||||
|
the least significant bytes correspond to bytes in our haystack at a lower
|
||||||
|
address.) That position, modulo `8`, gives us the pattern that the fingerprint
|
||||||
|
matches. That position, integer divided by `8`, also gives us the byte offset
|
||||||
|
that the fingerprint occurs in inside the 16 byte haystack block. Using those
|
||||||
|
two pieces of information, we can run a verification procedure that tries
|
||||||
|
to match all substrings containing that fingerprint at that position in the
|
||||||
|
haystack.
|
||||||
|
|
||||||
|
|
||||||
|
# Implementation notes
|
||||||
|
|
||||||
|
The problem with the algorithm as described above is that it uses a single byte
|
||||||
|
for a fingerprint. This will work well if the fingerprints are rare in the
|
||||||
|
haystack (e.g., capital letters or special characters in normal English text),
|
||||||
|
but if the fingerprints are common, you'll wind up spending too much time in
|
||||||
|
the verification step, which effectively negates the performance benefits of
|
||||||
|
scanning 16 bytes at a time. Remember, the key to the performance of this
|
||||||
|
algorithm is to do as little work as possible per 16 (or 32) bytes.
|
||||||
|
|
||||||
|
This algorithm can be extrapolated in a relatively straight-forward way to use
|
||||||
|
larger fingerprints. That is, instead of a single byte prefix, we might use a
|
||||||
|
two or three byte prefix. The implementation here implements N = {1, 2, 3}
|
||||||
|
and always picks the largest N possible. The rationale is that the bigger the
|
||||||
|
fingerprint, the fewer verification steps we'll do. Of course, if N is too
|
||||||
|
large, then we'll end up doing too much on each step.
|
||||||
|
|
||||||
|
The way to extend it is:
|
||||||
|
|
||||||
|
1. Add a mask for each byte in the fingerprint. (Remember that each mask is
|
||||||
|
composed of two SIMD vectors.) This results in a value of `C` for each byte
|
||||||
|
in the fingerprint while searching.
|
||||||
|
2. When testing each 16 (or 32) byte block, each value of `C` must be shifted
|
||||||
|
so that they are aligned. Once aligned, they should all be `AND`'d together.
|
||||||
|
This will give you only the bitsets corresponding to the full match of the
|
||||||
|
fingerprint. To do this, one needs to save the last byte (for N=2) or last
|
||||||
|
two bytes (for N=3) from the previous iteration, and then line them up with
|
||||||
|
the first one or two bytes of the next iteration.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Verification generally follows the procedure outlined above. The tricky parts
|
||||||
|
are in the right formulation of operations to get our bits out of our vectors.
|
||||||
|
We have a limited set of operations available to us on SIMD vectors as 128-bit
|
||||||
|
or 256-bit numbers, so we wind up needing to rip out 2 (or 4) 64-bit integers
|
||||||
|
from our vectors, and then run our verification step on each of those. The
|
||||||
|
verification step looks at the least significant bit set, and from its
|
||||||
|
position, we can derive the byte offset and bucket. (Again, as described
|
||||||
|
above.) Once we know the bucket, we do a fairly naive exhaustive search for
|
||||||
|
every literal in that bucket. (Hyperscan is a bit smarter here and uses a hash
|
||||||
|
table, but I haven't had time to thoroughly explore that. A few initial
|
||||||
|
half-hearted attempts resulted in worse performance.)
|
||||||
|
|
||||||
|
## AVX
|
||||||
|
|
||||||
|
The AVX version of Teddy extrapolates almost perfectly from the SSE version.
|
||||||
|
The only hickup is that PALIGNR is used to align chunks in the 16-bit version,
|
||||||
|
and there is no equivalent instruction in AVX. AVX does have VPALIGNR, but it
|
||||||
|
only works within 128-bit lanes. So there's a bit of tomfoolery to get around
|
||||||
|
this by shuffling the vectors before calling VPALIGNR.
|
||||||
|
|
||||||
|
The only other aspect to AVX is that since our masks are still fundamentally
|
||||||
|
16-bytes (0x0-0xF), they are duplicated to 32-bytes, so that they can apply to
|
||||||
|
32-byte chunks.
|
||||||
|
|
||||||
|
## Fat Teddy
|
||||||
|
|
||||||
|
In the version of Teddy described above, 8 buckets are used to group patterns
|
||||||
|
that we want to search for. However, when AVX is available, we can extend the
|
||||||
|
number of buckets to 16 by permitting each byte in our masks to use 16-bits
|
||||||
|
instead of 8-bits to represent the buckets it belongs to. (This variant is also
|
||||||
|
in Hyperscan.) However, what we give up is the ability to scan 32 bytes at a
|
||||||
|
time, even though we're using AVX. Instead, we have to scan 16 bytes at a time.
|
||||||
|
What we gain, though, is (hopefully) less work in our verification routine.
|
||||||
|
It patterns are more spread out across more buckets, then there should overall
|
||||||
|
be fewer false positives. In general, Fat Teddy permits us to grow our capacity
|
||||||
|
a bit and search for more literals before Teddy gets overwhelmed.
|
||||||
|
|
||||||
|
The tricky part of Fat Teddy is in how we adjust our masks and our verification
|
||||||
|
procedure. For the masks, we simply represent the first 8 buckets in each of
|
||||||
|
the low 16 bytes, and then the second 8 buckets in each of the high 16 bytes.
|
||||||
|
Then, in the search loop, instead of loading 32 bytes from the haystack, we
|
||||||
|
load the same 16 bytes from the haystack into both the low and high 16 byte
|
||||||
|
portions of our 256-bit vector. So for example, a mask might look like this:
|
||||||
|
|
||||||
|
bits: 00100001 00000000 ... 11000000 00000000 00000001 ... 00000000
|
||||||
|
byte: 31 30 16 15 14 0
|
||||||
|
offset: 15 14 0 15 14 0
|
||||||
|
buckets: 8-15 8-15 8-15 0-7 0-7 0-7
|
||||||
|
|
||||||
|
Where `byte` is the position in the vector (higher numbers corresponding to
|
||||||
|
more significant bits), `offset` is the corresponding position in the haystack
|
||||||
|
chunk, and `buckets` corresponds to the bucket assignments for that particular
|
||||||
|
byte.
|
||||||
|
|
||||||
|
In particular, notice that the bucket assignments for offset `0` are spread
|
||||||
|
out between bytes `0` and `16`. This works well for the chunk-by-chunk search
|
||||||
|
procedure, but verification really wants to process all bucket assignments for
|
||||||
|
each offset at once. Otherwise, we might wind up finding a match at offset
|
||||||
|
`1` in one the first 8 buckets, when we really should have reported a match
|
||||||
|
at offset `0` in one of the second 8 buckets. (Because we want the leftmost
|
||||||
|
match.)
|
||||||
|
|
||||||
|
Thus, for verification, we rearrange the above vector such that it is a
|
||||||
|
sequence of 16-bit integers, where the least significant 16-bit integer
|
||||||
|
corresponds to all of the bucket assignments for offset `0`. So with the
|
||||||
|
above vector, the least significant 16-bit integer would be
|
||||||
|
|
||||||
|
11000000 000000
|
||||||
|
|
||||||
|
which was taken from bytes `16` and `0`. Then the verification step pretty much
|
||||||
|
runs as described, except with 16 buckets instead of 8.
|
||||||
|
|
||||||
|
|
||||||
|
# References
|
||||||
|
|
||||||
|
- **[1]** [Hyperscan on GitHub](https://github.com/intel/hyperscan),
|
||||||
|
[webpage](https://www.hyperscan.io/)
|
||||||
|
- **[2a]** Ben-Kiki, O., Bille, P., Breslauer, D., Gasieniec, L., Grossi, R.,
|
||||||
|
& Weimann, O. (2011).
|
||||||
|
_Optimal packed string matching_.
|
||||||
|
In LIPIcs-Leibniz International Proceedings in Informatics (Vol. 13).
|
||||||
|
Schloss Dagstuhl-Leibniz-Zentrum fuer Informatik.
|
||||||
|
DOI: 10.4230/LIPIcs.FSTTCS.2011.423.
|
||||||
|
[PDF](https://drops.dagstuhl.de/opus/volltexte/2011/3355/pdf/37.pdf).
|
||||||
|
- **[2b]** Ben-Kiki, O., Bille, P., Breslauer, D., Ga̧sieniec, L., Grossi, R.,
|
||||||
|
& Weimann, O. (2014).
|
||||||
|
_Towards optimal packed string matching_.
|
||||||
|
Theoretical Computer Science, 525, 111-129.
|
||||||
|
DOI: 10.1016/j.tcs.2013.06.013.
|
||||||
|
[PDF](https://www.cs.haifa.ac.il/~oren/Publications/bpsm.pdf).
|
||||||
|
- **[3]** Bille, P. (2011).
|
||||||
|
_Fast searching in packed strings_.
|
||||||
|
Journal of Discrete Algorithms, 9(1), 49-56.
|
||||||
|
DOI: 10.1016/j.jda.2010.09.003.
|
||||||
|
[PDF](https://www.sciencedirect.com/science/article/pii/S1570866710000353).
|
||||||
|
- **[4a]** Faro, S., & Külekci, M. O. (2012, October).
|
||||||
|
_Fast multiple string matching using streaming SIMD extensions technology_.
|
||||||
|
In String Processing and Information Retrieval (pp. 217-228).
|
||||||
|
Springer Berlin Heidelberg.
|
||||||
|
DOI: 10.1007/978-3-642-34109-0_23.
|
||||||
|
[PDF](https://www.dmi.unict.it/faro/papers/conference/faro32.pdf).
|
||||||
|
- **[4b]** Faro, S., & Külekci, M. O. (2013, September).
|
||||||
|
_Towards a Very Fast Multiple String Matching Algorithm for Short Patterns_.
|
||||||
|
In Stringology (pp. 78-91).
|
||||||
|
[PDF](https://www.dmi.unict.it/faro/papers/conference/faro36.pdf).
|
||||||
|
- **[4c]** Faro, S., & Külekci, M. O. (2013, January).
|
||||||
|
_Fast packed string matching for short patterns_.
|
||||||
|
In Proceedings of the Meeting on Algorithm Engineering & Expermiments
|
||||||
|
(pp. 113-121).
|
||||||
|
Society for Industrial and Applied Mathematics.
|
||||||
|
[PDF](https://arxiv.org/pdf/1209.6449.pdf).
|
||||||
|
- **[4d]** Faro, S., & Külekci, M. O. (2014).
|
||||||
|
_Fast and flexible packed string matching_.
|
||||||
|
Journal of Discrete Algorithms, 28, 61-72.
|
||||||
|
DOI: 10.1016/j.jda.2014.07.003.
|
||||||
|
|
||||||
|
[1_u]: https://github.com/intel/hyperscan
|
||||||
|
[5_u]: https://software.intel.com/sites/landingpage/IntrinsicsGuide
|
||||||
792
third_party/crates/aho-corasick-1.1.5/src/packed/teddy/builder.rs
vendored
Normal file
792
third_party/crates/aho-corasick-1.1.5/src/packed/teddy/builder.rs
vendored
Normal file
@@ -0,0 +1,792 @@
|
|||||||
|
use core::{
|
||||||
|
fmt::Debug,
|
||||||
|
panic::{RefUnwindSafe, UnwindSafe},
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
|
||||||
|
use crate::packed::{ext::Pointer, pattern::Patterns, teddy::generic::Match};
|
||||||
|
|
||||||
|
/// A builder for constructing a Teddy matcher.
|
||||||
|
///
|
||||||
|
/// The builder primarily permits fine grained configuration of the Teddy
|
||||||
|
/// matcher. Most options are made only available for testing/benchmarking
|
||||||
|
/// purposes. In reality, options are automatically determined by the nature
|
||||||
|
/// and number of patterns given to the builder.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Builder {
|
||||||
|
/// When none, this is automatically determined. Otherwise, `false` means
|
||||||
|
/// slim Teddy is used (8 buckets) and `true` means fat Teddy is used
|
||||||
|
/// (16 buckets). Fat Teddy requires AVX2, so if that CPU feature isn't
|
||||||
|
/// available and Fat Teddy was requested, no matcher will be built.
|
||||||
|
only_fat: Option<bool>,
|
||||||
|
/// When none, this is automatically determined. Otherwise, `false` means
|
||||||
|
/// that 128-bit vectors will be used (up to SSSE3 instructions) where as
|
||||||
|
/// `true` means that 256-bit vectors will be used. As with `fat`, if
|
||||||
|
/// 256-bit vectors are requested and they aren't available, then a
|
||||||
|
/// searcher will not be built.
|
||||||
|
only_256bit: Option<bool>,
|
||||||
|
/// When true (the default), the number of patterns will be used as a
|
||||||
|
/// heuristic for refusing construction of a Teddy searcher. The point here
|
||||||
|
/// is that too many patterns can overwhelm Teddy. But this can be disabled
|
||||||
|
/// in cases where the caller knows better.
|
||||||
|
heuristic_pattern_limits: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Builder {
|
||||||
|
fn default() -> Builder {
|
||||||
|
Builder::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
/// Create a new builder for configuring a Teddy matcher.
|
||||||
|
pub(crate) fn new() -> Builder {
|
||||||
|
Builder {
|
||||||
|
only_fat: None,
|
||||||
|
only_256bit: None,
|
||||||
|
heuristic_pattern_limits: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a matcher for the set of patterns given. If a matcher could not
|
||||||
|
/// be built, then `None` is returned.
|
||||||
|
///
|
||||||
|
/// Generally, a matcher isn't built if the necessary CPU features aren't
|
||||||
|
/// available, an unsupported target or if the searcher is believed to be
|
||||||
|
/// slower than standard techniques (i.e., if there are too many literals).
|
||||||
|
pub(crate) fn build(&self, patterns: Arc<Patterns>) -> Option<Searcher> {
|
||||||
|
self.build_imp(patterns)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Require the use of Fat (true) or Slim (false) Teddy. Fat Teddy uses
|
||||||
|
/// 16 buckets where as Slim Teddy uses 8 buckets. More buckets are useful
|
||||||
|
/// for a larger set of literals.
|
||||||
|
///
|
||||||
|
/// `None` is the default, which results in an automatic selection based
|
||||||
|
/// on the number of literals and available CPU features.
|
||||||
|
pub(crate) fn only_fat(&mut self, yes: Option<bool>) -> &mut Builder {
|
||||||
|
self.only_fat = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request the use of 256-bit vectors (true) or 128-bit vectors (false).
|
||||||
|
/// Generally, a larger vector size is better since it either permits
|
||||||
|
/// matching more patterns or matching more bytes in the haystack at once.
|
||||||
|
///
|
||||||
|
/// `None` is the default, which results in an automatic selection based on
|
||||||
|
/// the number of literals and available CPU features.
|
||||||
|
pub(crate) fn only_256bit(&mut self, yes: Option<bool>) -> &mut Builder {
|
||||||
|
self.only_256bit = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request that heuristic limitations on the number of patterns be
|
||||||
|
/// employed. This useful to disable for benchmarking where one wants to
|
||||||
|
/// explore how Teddy performs on large number of patterns even if the
|
||||||
|
/// heuristics would otherwise refuse construction.
|
||||||
|
///
|
||||||
|
/// This is enabled by default.
|
||||||
|
pub(crate) fn heuristic_pattern_limits(
|
||||||
|
&mut self,
|
||||||
|
yes: bool,
|
||||||
|
) -> &mut Builder {
|
||||||
|
self.heuristic_pattern_limits = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_imp(&self, patterns: Arc<Patterns>) -> Option<Searcher> {
|
||||||
|
let patlimit = self.heuristic_pattern_limits;
|
||||||
|
// There's no particular reason why we limit ourselves to little endian
|
||||||
|
// here, but it seems likely that some parts of Teddy as they are
|
||||||
|
// currently written (e.g., the uses of `trailing_zeros`) are likely
|
||||||
|
// wrong on non-little-endian targets. Such things are likely easy to
|
||||||
|
// fix, but at the time of writing (2023/09/18), I actually do not know
|
||||||
|
// how to test this code on a big-endian target. So for now, we're
|
||||||
|
// conservative and just bail out.
|
||||||
|
if !cfg!(target_endian = "little") {
|
||||||
|
debug!("skipping Teddy because target isn't little endian");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// Too many patterns will overwhelm Teddy and likely lead to slow
|
||||||
|
// downs, typically in the verification step.
|
||||||
|
if patlimit && patterns.len() > 64 {
|
||||||
|
debug!("skipping Teddy because of too many patterns");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
|
||||||
|
{
|
||||||
|
use self::x86_64::{FatAVX2, SlimAVX2, SlimSSSE3};
|
||||||
|
|
||||||
|
let mask_len = core::cmp::min(4, patterns.minimum_len());
|
||||||
|
let beefy = patterns.len() > 32;
|
||||||
|
let has_avx2 = self::x86_64::is_available_avx2();
|
||||||
|
let has_ssse3 = has_avx2 || self::x86_64::is_available_ssse3();
|
||||||
|
let use_avx2 = if self.only_256bit == Some(true) {
|
||||||
|
if !has_avx2 {
|
||||||
|
debug!(
|
||||||
|
"skipping Teddy because avx2 was demanded but unavailable"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else if self.only_256bit == Some(false) {
|
||||||
|
if !has_ssse3 {
|
||||||
|
debug!(
|
||||||
|
"skipping Teddy because ssse3 was demanded but unavailable"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} else if !has_ssse3 && !has_avx2 {
|
||||||
|
debug!(
|
||||||
|
"skipping Teddy because ssse3 and avx2 are unavailable"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
has_avx2
|
||||||
|
};
|
||||||
|
let fat = match self.only_fat {
|
||||||
|
None => use_avx2 && beefy,
|
||||||
|
Some(false) => false,
|
||||||
|
Some(true) if !use_avx2 => {
|
||||||
|
debug!(
|
||||||
|
"skipping Teddy because fat was demanded, but fat \
|
||||||
|
Teddy requires avx2 which is unavailable"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(true) => true,
|
||||||
|
};
|
||||||
|
// Just like for aarch64, it's possible that too many patterns will
|
||||||
|
// overhwelm Teddy. Unlike aarch64 though, we have Fat teddy which
|
||||||
|
// helps things scale a bit more by spreading patterns over more
|
||||||
|
// buckets.
|
||||||
|
//
|
||||||
|
// These thresholds were determined by looking at the measurements
|
||||||
|
// for the rust/aho-corasick/packed/leftmost-first and
|
||||||
|
// rust/aho-corasick/dfa/leftmost-first engines on the `teddy/`
|
||||||
|
// benchmarks.
|
||||||
|
if patlimit && mask_len == 1 && patterns.len() > 16 {
|
||||||
|
debug!(
|
||||||
|
"skipping Teddy (mask len: 1) because there are \
|
||||||
|
too many patterns",
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
match (mask_len, use_avx2, fat) {
|
||||||
|
(1, false, _) => {
|
||||||
|
debug!("Teddy choice: 128-bit slim, 1 byte");
|
||||||
|
SlimSSSE3::<1>::new(&patterns)
|
||||||
|
}
|
||||||
|
(1, true, false) => {
|
||||||
|
debug!("Teddy choice: 256-bit slim, 1 byte");
|
||||||
|
SlimAVX2::<1>::new(&patterns)
|
||||||
|
}
|
||||||
|
(1, true, true) => {
|
||||||
|
debug!("Teddy choice: 256-bit fat, 1 byte");
|
||||||
|
FatAVX2::<1>::new(&patterns)
|
||||||
|
}
|
||||||
|
(2, false, _) => {
|
||||||
|
debug!("Teddy choice: 128-bit slim, 2 bytes");
|
||||||
|
SlimSSSE3::<2>::new(&patterns)
|
||||||
|
}
|
||||||
|
(2, true, false) => {
|
||||||
|
debug!("Teddy choice: 256-bit slim, 2 bytes");
|
||||||
|
SlimAVX2::<2>::new(&patterns)
|
||||||
|
}
|
||||||
|
(2, true, true) => {
|
||||||
|
debug!("Teddy choice: 256-bit fat, 2 bytes");
|
||||||
|
FatAVX2::<2>::new(&patterns)
|
||||||
|
}
|
||||||
|
(3, false, _) => {
|
||||||
|
debug!("Teddy choice: 128-bit slim, 3 bytes");
|
||||||
|
SlimSSSE3::<3>::new(&patterns)
|
||||||
|
}
|
||||||
|
(3, true, false) => {
|
||||||
|
debug!("Teddy choice: 256-bit slim, 3 bytes");
|
||||||
|
SlimAVX2::<3>::new(&patterns)
|
||||||
|
}
|
||||||
|
(3, true, true) => {
|
||||||
|
debug!("Teddy choice: 256-bit fat, 3 bytes");
|
||||||
|
FatAVX2::<3>::new(&patterns)
|
||||||
|
}
|
||||||
|
(4, false, _) => {
|
||||||
|
debug!("Teddy choice: 128-bit slim, 4 bytes");
|
||||||
|
SlimSSSE3::<4>::new(&patterns)
|
||||||
|
}
|
||||||
|
(4, true, false) => {
|
||||||
|
debug!("Teddy choice: 256-bit slim, 4 bytes");
|
||||||
|
SlimAVX2::<4>::new(&patterns)
|
||||||
|
}
|
||||||
|
(4, true, true) => {
|
||||||
|
debug!("Teddy choice: 256-bit fat, 4 bytes");
|
||||||
|
FatAVX2::<4>::new(&patterns)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!("no supported Teddy configuration found");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(
|
||||||
|
target_arch = "aarch64",
|
||||||
|
target_feature = "neon",
|
||||||
|
target_endian = "little"
|
||||||
|
))]
|
||||||
|
{
|
||||||
|
use self::aarch64::SlimNeon;
|
||||||
|
|
||||||
|
let mask_len = core::cmp::min(4, patterns.minimum_len());
|
||||||
|
if self.only_256bit == Some(true) {
|
||||||
|
debug!(
|
||||||
|
"skipping Teddy because 256-bits were demanded \
|
||||||
|
but unavailable"
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if self.only_fat == Some(true) {
|
||||||
|
debug!(
|
||||||
|
"skipping Teddy because fat was demanded but unavailable"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Since we don't have Fat teddy in aarch64 (I think we'd want at
|
||||||
|
// least 256-bit vectors for that), we need to be careful not to
|
||||||
|
// allow too many patterns as it might overwhelm Teddy. Generally
|
||||||
|
// speaking, as the mask length goes up, the more patterns we can
|
||||||
|
// handle because the mask length results in fewer candidates
|
||||||
|
// generated.
|
||||||
|
//
|
||||||
|
// These thresholds were determined by looking at the measurements
|
||||||
|
// for the rust/aho-corasick/packed/leftmost-first and
|
||||||
|
// rust/aho-corasick/dfa/leftmost-first engines on the `teddy/`
|
||||||
|
// benchmarks.
|
||||||
|
match mask_len {
|
||||||
|
1 => {
|
||||||
|
if patlimit && patterns.len() > 16 {
|
||||||
|
debug!(
|
||||||
|
"skipping Teddy (mask len: 1) because there are \
|
||||||
|
too many patterns",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
debug!("Teddy choice: 128-bit slim, 1 byte");
|
||||||
|
SlimNeon::<1>::new(&patterns)
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
if patlimit && patterns.len() > 32 {
|
||||||
|
debug!(
|
||||||
|
"skipping Teddy (mask len: 2) because there are \
|
||||||
|
too many patterns",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
debug!("Teddy choice: 128-bit slim, 2 bytes");
|
||||||
|
SlimNeon::<2>::new(&patterns)
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
if patlimit && patterns.len() > 48 {
|
||||||
|
debug!(
|
||||||
|
"skipping Teddy (mask len: 3) because there are \
|
||||||
|
too many patterns",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
debug!("Teddy choice: 128-bit slim, 3 bytes");
|
||||||
|
SlimNeon::<3>::new(&patterns)
|
||||||
|
}
|
||||||
|
4 => {
|
||||||
|
debug!("Teddy choice: 128-bit slim, 4 bytes");
|
||||||
|
SlimNeon::<4>::new(&patterns)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!("no supported Teddy configuration found");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(any(
|
||||||
|
all(target_arch = "x86_64", target_feature = "sse2"),
|
||||||
|
all(
|
||||||
|
target_arch = "aarch64",
|
||||||
|
target_feature = "neon",
|
||||||
|
target_endian = "little"
|
||||||
|
)
|
||||||
|
)))]
|
||||||
|
{
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A searcher that dispatches to one of several possible Teddy variants.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Searcher {
|
||||||
|
/// The Teddy variant we use. We use dynamic dispatch under the theory that
|
||||||
|
/// it results in better codegen then a enum, although this is a specious
|
||||||
|
/// claim.
|
||||||
|
///
|
||||||
|
/// This `Searcher` is essentially a wrapper for a `SearcherT` trait
|
||||||
|
/// object. We just make `memory_usage` and `minimum_len` available without
|
||||||
|
/// going through dynamic dispatch.
|
||||||
|
imp: Arc<dyn SearcherT>,
|
||||||
|
/// Total heap memory used by the Teddy variant.
|
||||||
|
memory_usage: usize,
|
||||||
|
/// The minimum haystack length this searcher can handle. It is intended
|
||||||
|
/// for callers to use some other search routine (such as Rabin-Karp) in
|
||||||
|
/// cases where the haystack (or remainer of the haystack) is too short.
|
||||||
|
minimum_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Searcher {
|
||||||
|
/// Look for the leftmost occurrence of any pattern in this search in the
|
||||||
|
/// given haystack starting at the given position.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This panics when `haystack[at..].len()` is less than the minimum length
|
||||||
|
/// for this haystack.
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn find(
|
||||||
|
&self,
|
||||||
|
haystack: &[u8],
|
||||||
|
at: usize,
|
||||||
|
) -> Option<crate::Match> {
|
||||||
|
// SAFETY: The Teddy implementations all require a minimum haystack
|
||||||
|
// length, and this is required for safety. Therefore, we assert it
|
||||||
|
// here in order to make this method sound.
|
||||||
|
assert!(haystack[at..].len() >= self.minimum_len);
|
||||||
|
let hayptr = haystack.as_ptr();
|
||||||
|
// SAFETY: Construction of the searcher guarantees that we are able
|
||||||
|
// to run it in the current environment (i.e., we won't get an AVX2
|
||||||
|
// searcher on a x86-64 CPU without AVX2 support). Also, the pointers
|
||||||
|
// are valid as they are derived directly from a borrowed slice.
|
||||||
|
let teddym = unsafe {
|
||||||
|
self.imp.find(hayptr.add(at), hayptr.add(haystack.len()))?
|
||||||
|
};
|
||||||
|
let start = teddym.start().as_usize().wrapping_sub(hayptr.as_usize());
|
||||||
|
let end = teddym.end().as_usize().wrapping_sub(hayptr.as_usize());
|
||||||
|
let span = crate::Span { start, end };
|
||||||
|
// OK because we won't permit the construction of a searcher that
|
||||||
|
// could report a pattern ID bigger than what can fit in the crate-wide
|
||||||
|
// PatternID type.
|
||||||
|
let pid = crate::PatternID::new_unchecked(teddym.pattern().as_usize());
|
||||||
|
let m = crate::Match::new(pid, span);
|
||||||
|
Some(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the approximate total amount of heap used by this type, in
|
||||||
|
/// units of bytes.
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn memory_usage(&self) -> usize {
|
||||||
|
self.memory_usage
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum length, in bytes, that a haystack must be in order
|
||||||
|
/// to use it with this searcher.
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn minimum_len(&self) -> usize {
|
||||||
|
self.minimum_len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait that provides dynamic dispatch over the different possible Teddy
|
||||||
|
/// variants on the same algorithm.
|
||||||
|
///
|
||||||
|
/// On `x86_64` for example, it isn't known until runtime which of 12 possible
|
||||||
|
/// variants will be used. One might use one of the four slim 128-bit vector
|
||||||
|
/// variants, or one of the four 256-bit vector variants or even one of the
|
||||||
|
/// four fat 256-bit vector variants.
|
||||||
|
///
|
||||||
|
/// Since this choice is generally made when the Teddy searcher is constructed
|
||||||
|
/// and this choice is based on the patterns given and what the current CPU
|
||||||
|
/// supports, it follows that there must be some kind of indirection at search
|
||||||
|
/// time that "selects" the variant chosen at build time.
|
||||||
|
///
|
||||||
|
/// There are a few different ways to go about this. One approach is to use an
|
||||||
|
/// enum. It works fine, but in my experiments, this generally results in worse
|
||||||
|
/// codegen. Another approach, which is what we use here, is dynamic dispatch
|
||||||
|
/// via a trait object. We basically implement this trait for each possible
|
||||||
|
/// variant, select the variant we want at build time and convert it to a
|
||||||
|
/// trait object for use at search time.
|
||||||
|
///
|
||||||
|
/// Another approach is to use function pointers and stick each of the possible
|
||||||
|
/// variants into a union. This is essentially isomorphic to the dynamic
|
||||||
|
/// dispatch approach, but doesn't require any allocations. Since this crate
|
||||||
|
/// requires `alloc`, there's no real reason (AFAIK) to go down this path. (The
|
||||||
|
/// `memchr` crate does this.)
|
||||||
|
trait SearcherT:
|
||||||
|
Debug + Send + Sync + UnwindSafe + RefUnwindSafe + 'static
|
||||||
|
{
|
||||||
|
/// Execute a search on the given haystack (identified by `start` and `end`
|
||||||
|
/// raw pointers).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Essentially, the `start` and `end` pointers must be valid and point
|
||||||
|
/// to a haystack one can read. As long as you derive them from, for
|
||||||
|
/// example, a `&[u8]`, they should automatically satisfy all of the safety
|
||||||
|
/// obligations:
|
||||||
|
///
|
||||||
|
/// * Both `start` and `end` must be valid for reads.
|
||||||
|
/// * Both `start` and `end` must point to an initialized value.
|
||||||
|
/// * Both `start` and `end` must point to the same allocated object and
|
||||||
|
/// must either be in bounds or at most one byte past the end of the
|
||||||
|
/// allocated object.
|
||||||
|
/// * Both `start` and `end` must be _derived from_ a pointer to the same
|
||||||
|
/// object.
|
||||||
|
/// * The distance between `start` and `end` must not overflow `isize`.
|
||||||
|
/// * The distance being in bounds must not rely on "wrapping around" the
|
||||||
|
/// address space.
|
||||||
|
/// * It must be the case that `start <= end`.
|
||||||
|
/// * `end - start` must be greater than the minimum length for this
|
||||||
|
/// searcher.
|
||||||
|
///
|
||||||
|
/// Also, it is expected that implementations of this trait will tag this
|
||||||
|
/// method with a `target_feature` attribute. Callers must ensure that
|
||||||
|
/// they are executing this method in an environment where that attribute
|
||||||
|
/// is valid.
|
||||||
|
unsafe fn find(&self, start: *const u8, end: *const u8) -> Option<Match>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", target_feature = "sse2"))]
|
||||||
|
mod x86_64 {
|
||||||
|
use core::arch::x86_64::{__m128i, __m256i};
|
||||||
|
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
|
||||||
|
use crate::packed::{
|
||||||
|
ext::Pointer,
|
||||||
|
pattern::Patterns,
|
||||||
|
teddy::generic::{self, Match},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Searcher, SearcherT};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(super) struct SlimSSSE3<const BYTES: usize> {
|
||||||
|
slim128: generic::Slim<__m128i, BYTES>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines SlimSSSE3 wrapper functions for 1, 2, 3 and 4 bytes.
|
||||||
|
macro_rules! slim_ssse3 {
|
||||||
|
($len:expr) => {
|
||||||
|
impl SlimSSSE3<$len> {
|
||||||
|
/// Creates a new searcher using "slim" Teddy with 128-bit
|
||||||
|
/// vectors. If SSSE3 is not available in the current
|
||||||
|
/// environment, then this returns `None`.
|
||||||
|
pub(super) fn new(
|
||||||
|
patterns: &Arc<Patterns>,
|
||||||
|
) -> Option<Searcher> {
|
||||||
|
if !is_available_ssse3() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(unsafe { SlimSSSE3::<$len>::new_unchecked(patterns) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new searcher using "slim" Teddy with 256-bit
|
||||||
|
/// vectors without checking whether SSSE3 is available or not.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Callers must ensure that SSSE3 is available in the current
|
||||||
|
/// environment.
|
||||||
|
#[target_feature(enable = "ssse3")]
|
||||||
|
unsafe fn new_unchecked(patterns: &Arc<Patterns>) -> Searcher {
|
||||||
|
let slim128 = generic::Slim::<__m128i, $len>::new(
|
||||||
|
Arc::clone(patterns),
|
||||||
|
);
|
||||||
|
let memory_usage = slim128.memory_usage();
|
||||||
|
let minimum_len = slim128.minimum_len();
|
||||||
|
let imp = Arc::new(SlimSSSE3 { slim128 });
|
||||||
|
Searcher { imp, memory_usage, minimum_len }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearcherT for SlimSSSE3<$len> {
|
||||||
|
#[target_feature(enable = "ssse3")]
|
||||||
|
#[inline]
|
||||||
|
unsafe fn find(
|
||||||
|
&self,
|
||||||
|
start: *const u8,
|
||||||
|
end: *const u8,
|
||||||
|
) -> Option<Match> {
|
||||||
|
// SAFETY: All obligations except for `target_feature` are
|
||||||
|
// passed to the caller. Our use of `target_feature` is
|
||||||
|
// safe because construction of this type requires that the
|
||||||
|
// requisite target features are available.
|
||||||
|
self.slim128.find(start, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
slim_ssse3!(1);
|
||||||
|
slim_ssse3!(2);
|
||||||
|
slim_ssse3!(3);
|
||||||
|
slim_ssse3!(4);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(super) struct SlimAVX2<const BYTES: usize> {
|
||||||
|
slim128: generic::Slim<__m128i, BYTES>,
|
||||||
|
slim256: generic::Slim<__m256i, BYTES>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines SlimAVX2 wrapper functions for 1, 2, 3 and 4 bytes.
|
||||||
|
macro_rules! slim_avx2 {
|
||||||
|
($len:expr) => {
|
||||||
|
impl SlimAVX2<$len> {
|
||||||
|
/// Creates a new searcher using "slim" Teddy with 256-bit
|
||||||
|
/// vectors. If AVX2 is not available in the current
|
||||||
|
/// environment, then this returns `None`.
|
||||||
|
pub(super) fn new(
|
||||||
|
patterns: &Arc<Patterns>,
|
||||||
|
) -> Option<Searcher> {
|
||||||
|
if !is_available_avx2() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(unsafe { SlimAVX2::<$len>::new_unchecked(patterns) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new searcher using "slim" Teddy with 256-bit
|
||||||
|
/// vectors without checking whether AVX2 is available or not.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Callers must ensure that AVX2 is available in the current
|
||||||
|
/// environment.
|
||||||
|
#[target_feature(enable = "avx2")]
|
||||||
|
unsafe fn new_unchecked(patterns: &Arc<Patterns>) -> Searcher {
|
||||||
|
let slim128 = generic::Slim::<__m128i, $len>::new(
|
||||||
|
Arc::clone(&patterns),
|
||||||
|
);
|
||||||
|
let slim256 = generic::Slim::<__m256i, $len>::new(
|
||||||
|
Arc::clone(&patterns),
|
||||||
|
);
|
||||||
|
let memory_usage =
|
||||||
|
slim128.memory_usage() + slim256.memory_usage();
|
||||||
|
let minimum_len = slim128.minimum_len();
|
||||||
|
let imp = Arc::new(SlimAVX2 { slim128, slim256 });
|
||||||
|
Searcher { imp, memory_usage, minimum_len }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearcherT for SlimAVX2<$len> {
|
||||||
|
#[target_feature(enable = "avx2")]
|
||||||
|
#[inline]
|
||||||
|
unsafe fn find(
|
||||||
|
&self,
|
||||||
|
start: *const u8,
|
||||||
|
end: *const u8,
|
||||||
|
) -> Option<Match> {
|
||||||
|
// SAFETY: All obligations except for `target_feature` are
|
||||||
|
// passed to the caller. Our use of `target_feature` is
|
||||||
|
// safe because construction of this type requires that the
|
||||||
|
// requisite target features are available.
|
||||||
|
let len = end.distance(start);
|
||||||
|
if len < self.slim256.minimum_len() {
|
||||||
|
self.slim128.find(start, end)
|
||||||
|
} else {
|
||||||
|
self.slim256.find(start, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
slim_avx2!(1);
|
||||||
|
slim_avx2!(2);
|
||||||
|
slim_avx2!(3);
|
||||||
|
slim_avx2!(4);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(super) struct FatAVX2<const BYTES: usize> {
|
||||||
|
fat256: generic::Fat<__m256i, BYTES>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines SlimAVX2 wrapper functions for 1, 2, 3 and 4 bytes.
|
||||||
|
macro_rules! fat_avx2 {
|
||||||
|
($len:expr) => {
|
||||||
|
impl FatAVX2<$len> {
|
||||||
|
/// Creates a new searcher using "slim" Teddy with 256-bit
|
||||||
|
/// vectors. If AVX2 is not available in the current
|
||||||
|
/// environment, then this returns `None`.
|
||||||
|
pub(super) fn new(
|
||||||
|
patterns: &Arc<Patterns>,
|
||||||
|
) -> Option<Searcher> {
|
||||||
|
if !is_available_avx2() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(unsafe { FatAVX2::<$len>::new_unchecked(patterns) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new searcher using "slim" Teddy with 256-bit
|
||||||
|
/// vectors without checking whether AVX2 is available or not.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Callers must ensure that AVX2 is available in the current
|
||||||
|
/// environment.
|
||||||
|
#[target_feature(enable = "avx2")]
|
||||||
|
unsafe fn new_unchecked(patterns: &Arc<Patterns>) -> Searcher {
|
||||||
|
let fat256 = generic::Fat::<__m256i, $len>::new(
|
||||||
|
Arc::clone(&patterns),
|
||||||
|
);
|
||||||
|
let memory_usage = fat256.memory_usage();
|
||||||
|
let minimum_len = fat256.minimum_len();
|
||||||
|
let imp = Arc::new(FatAVX2 { fat256 });
|
||||||
|
Searcher { imp, memory_usage, minimum_len }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearcherT for FatAVX2<$len> {
|
||||||
|
#[target_feature(enable = "avx2")]
|
||||||
|
#[inline]
|
||||||
|
unsafe fn find(
|
||||||
|
&self,
|
||||||
|
start: *const u8,
|
||||||
|
end: *const u8,
|
||||||
|
) -> Option<Match> {
|
||||||
|
// SAFETY: All obligations except for `target_feature` are
|
||||||
|
// passed to the caller. Our use of `target_feature` is
|
||||||
|
// safe because construction of this type requires that the
|
||||||
|
// requisite target features are available.
|
||||||
|
self.fat256.find(start, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fat_avx2!(1);
|
||||||
|
fat_avx2!(2);
|
||||||
|
fat_avx2!(3);
|
||||||
|
fat_avx2!(4);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn is_available_ssse3() -> bool {
|
||||||
|
#[cfg(not(target_feature = "sse2"))]
|
||||||
|
{
|
||||||
|
false
|
||||||
|
}
|
||||||
|
#[cfg(target_feature = "sse2")]
|
||||||
|
{
|
||||||
|
#[cfg(target_feature = "ssse3")]
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
#[cfg(not(target_feature = "ssse3"))]
|
||||||
|
{
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
{
|
||||||
|
std::is_x86_feature_detected!("ssse3")
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
{
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn is_available_avx2() -> bool {
|
||||||
|
#[cfg(not(target_feature = "sse2"))]
|
||||||
|
{
|
||||||
|
false
|
||||||
|
}
|
||||||
|
#[cfg(target_feature = "sse2")]
|
||||||
|
{
|
||||||
|
#[cfg(target_feature = "avx2")]
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
#[cfg(not(target_feature = "avx2"))]
|
||||||
|
{
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
{
|
||||||
|
std::is_x86_feature_detected!("avx2")
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
{
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
target_arch = "aarch64",
|
||||||
|
target_feature = "neon",
|
||||||
|
target_endian = "little"
|
||||||
|
))]
|
||||||
|
mod aarch64 {
|
||||||
|
use core::arch::aarch64::uint8x16_t;
|
||||||
|
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
|
||||||
|
use crate::packed::{
|
||||||
|
pattern::Patterns,
|
||||||
|
teddy::generic::{self, Match},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Searcher, SearcherT};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(super) struct SlimNeon<const BYTES: usize> {
|
||||||
|
slim128: generic::Slim<uint8x16_t, BYTES>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines SlimSSSE3 wrapper functions for 1, 2, 3 and 4 bytes.
|
||||||
|
macro_rules! slim_neon {
|
||||||
|
($len:expr) => {
|
||||||
|
impl SlimNeon<$len> {
|
||||||
|
/// Creates a new searcher using "slim" Teddy with 128-bit
|
||||||
|
/// vectors. If SSSE3 is not available in the current
|
||||||
|
/// environment, then this returns `None`.
|
||||||
|
pub(super) fn new(
|
||||||
|
patterns: &Arc<Patterns>,
|
||||||
|
) -> Option<Searcher> {
|
||||||
|
Some(unsafe { SlimNeon::<$len>::new_unchecked(patterns) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new searcher using "slim" Teddy with 256-bit
|
||||||
|
/// vectors without checking whether SSSE3 is available or not.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Callers must ensure that SSSE3 is available in the current
|
||||||
|
/// environment.
|
||||||
|
#[target_feature(enable = "neon")]
|
||||||
|
unsafe fn new_unchecked(patterns: &Arc<Patterns>) -> Searcher {
|
||||||
|
let slim128 = generic::Slim::<uint8x16_t, $len>::new(
|
||||||
|
Arc::clone(patterns),
|
||||||
|
);
|
||||||
|
let memory_usage = slim128.memory_usage();
|
||||||
|
let minimum_len = slim128.minimum_len();
|
||||||
|
let imp = Arc::new(SlimNeon { slim128 });
|
||||||
|
Searcher { imp, memory_usage, minimum_len }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearcherT for SlimNeon<$len> {
|
||||||
|
#[target_feature(enable = "neon")]
|
||||||
|
#[inline]
|
||||||
|
unsafe fn find(
|
||||||
|
&self,
|
||||||
|
start: *const u8,
|
||||||
|
end: *const u8,
|
||||||
|
) -> Option<Match> {
|
||||||
|
// SAFETY: All obligations except for `target_feature` are
|
||||||
|
// passed to the caller. Our use of `target_feature` is
|
||||||
|
// safe because construction of this type requires that the
|
||||||
|
// requisite target features are available.
|
||||||
|
self.slim128.find(start, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
slim_neon!(1);
|
||||||
|
slim_neon!(2);
|
||||||
|
slim_neon!(3);
|
||||||
|
slim_neon!(4);
|
||||||
|
}
|
||||||
1382
third_party/crates/aho-corasick-1.1.5/src/packed/teddy/generic.rs
vendored
Normal file
1382
third_party/crates/aho-corasick-1.1.5/src/packed/teddy/generic.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
third_party/crates/aho-corasick-1.1.5/src/packed/teddy/mod.rs
vendored
Normal file
9
third_party/crates/aho-corasick-1.1.5/src/packed/teddy/mod.rs
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// Regrettable, but Teddy stuff just isn't used on all targets. And for some
|
||||||
|
// targets, like aarch64, only "slim" Teddy is used and so "fat" Teddy gets a
|
||||||
|
// bunch of dead-code warnings. Just not worth trying to squash them. Blech.
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
pub(crate) use self::builder::{Builder, Searcher};
|
||||||
|
|
||||||
|
mod builder;
|
||||||
|
mod generic;
|
||||||
583
third_party/crates/aho-corasick-1.1.5/src/packed/tests.rs
vendored
Normal file
583
third_party/crates/aho-corasick-1.1.5/src/packed/tests.rs
vendored
Normal file
@@ -0,0 +1,583 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use alloc::{
|
||||||
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
packed::{Config, MatchKind},
|
||||||
|
util::search::Match,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A description of a single test against a multi-pattern searcher.
|
||||||
|
///
|
||||||
|
/// A single test may not necessarily pass on every configuration of a
|
||||||
|
/// searcher. The tests are categorized and grouped appropriately below.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
struct SearchTest {
|
||||||
|
/// The name of this test, for debugging.
|
||||||
|
name: &'static str,
|
||||||
|
/// The patterns to search for.
|
||||||
|
patterns: &'static [&'static str],
|
||||||
|
/// The text to search.
|
||||||
|
haystack: &'static str,
|
||||||
|
/// Each match is a triple of (pattern_index, start, end), where
|
||||||
|
/// pattern_index is an index into `patterns` and `start`/`end` are indices
|
||||||
|
/// into `haystack`.
|
||||||
|
matches: &'static [(usize, usize, usize)],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SearchTestOwned {
|
||||||
|
offset: usize,
|
||||||
|
name: String,
|
||||||
|
patterns: Vec<String>,
|
||||||
|
haystack: String,
|
||||||
|
matches: Vec<(usize, usize, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SearchTest {
|
||||||
|
fn variations(&self) -> Vec<SearchTestOwned> {
|
||||||
|
let count = if cfg!(miri) { 1 } else { 261 };
|
||||||
|
let mut tests = vec![];
|
||||||
|
for i in 0..count {
|
||||||
|
tests.push(self.offset_prefix(i));
|
||||||
|
tests.push(self.offset_suffix(i));
|
||||||
|
tests.push(self.offset_both(i));
|
||||||
|
}
|
||||||
|
tests
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_both(&self, off: usize) -> SearchTestOwned {
|
||||||
|
SearchTestOwned {
|
||||||
|
offset: off,
|
||||||
|
name: self.name.to_string(),
|
||||||
|
patterns: self.patterns.iter().map(|s| s.to_string()).collect(),
|
||||||
|
haystack: format!(
|
||||||
|
"{}{}{}",
|
||||||
|
"Z".repeat(off),
|
||||||
|
self.haystack,
|
||||||
|
"Z".repeat(off)
|
||||||
|
),
|
||||||
|
matches: self
|
||||||
|
.matches
|
||||||
|
.iter()
|
||||||
|
.map(|&(id, s, e)| (id, s + off, e + off))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_prefix(&self, off: usize) -> SearchTestOwned {
|
||||||
|
SearchTestOwned {
|
||||||
|
offset: off,
|
||||||
|
name: self.name.to_string(),
|
||||||
|
patterns: self.patterns.iter().map(|s| s.to_string()).collect(),
|
||||||
|
haystack: format!("{}{}", "Z".repeat(off), self.haystack),
|
||||||
|
matches: self
|
||||||
|
.matches
|
||||||
|
.iter()
|
||||||
|
.map(|&(id, s, e)| (id, s + off, e + off))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_suffix(&self, off: usize) -> SearchTestOwned {
|
||||||
|
SearchTestOwned {
|
||||||
|
offset: off,
|
||||||
|
name: self.name.to_string(),
|
||||||
|
patterns: self.patterns.iter().map(|s| s.to_string()).collect(),
|
||||||
|
haystack: format!("{}{}", self.haystack, "Z".repeat(off)),
|
||||||
|
matches: self.matches.to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Short-hand constructor for SearchTest. We use it a lot below.
|
||||||
|
macro_rules! t {
|
||||||
|
($name:ident, $patterns:expr, $haystack:expr, $matches:expr) => {
|
||||||
|
SearchTest {
|
||||||
|
name: stringify!($name),
|
||||||
|
patterns: $patterns,
|
||||||
|
haystack: $haystack,
|
||||||
|
matches: $matches,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A collection of test groups.
|
||||||
|
type TestCollection = &'static [&'static [SearchTest]];
|
||||||
|
|
||||||
|
// Define several collections corresponding to the different type of match
|
||||||
|
// semantics supported. These collections have some overlap, but each
|
||||||
|
// collection should have some tests that no other collection has.
|
||||||
|
|
||||||
|
/// Tests for leftmost-first match semantics.
|
||||||
|
const PACKED_LEFTMOST_FIRST: TestCollection =
|
||||||
|
&[BASICS, LEFTMOST, LEFTMOST_FIRST, REGRESSION, TEDDY];
|
||||||
|
|
||||||
|
/// Tests for leftmost-longest match semantics.
|
||||||
|
const PACKED_LEFTMOST_LONGEST: TestCollection =
|
||||||
|
&[BASICS, LEFTMOST, LEFTMOST_LONGEST, REGRESSION, TEDDY];
|
||||||
|
|
||||||
|
// Now define the individual tests that make up the collections above.
|
||||||
|
|
||||||
|
/// A collection of tests for the that should always be true regardless of
|
||||||
|
/// match semantics. That is, all combinations of leftmost-{first, longest}
|
||||||
|
/// should produce the same answer.
|
||||||
|
const BASICS: &'static [SearchTest] = &[
|
||||||
|
t!(basic001, &["a"], "", &[]),
|
||||||
|
t!(basic010, &["a"], "a", &[(0, 0, 1)]),
|
||||||
|
t!(basic020, &["a"], "aa", &[(0, 0, 1), (0, 1, 2)]),
|
||||||
|
t!(basic030, &["a"], "aaa", &[(0, 0, 1), (0, 1, 2), (0, 2, 3)]),
|
||||||
|
t!(basic040, &["a"], "aba", &[(0, 0, 1), (0, 2, 3)]),
|
||||||
|
t!(basic050, &["a"], "bba", &[(0, 2, 3)]),
|
||||||
|
t!(basic060, &["a"], "bbb", &[]),
|
||||||
|
t!(basic070, &["a"], "bababbbba", &[(0, 1, 2), (0, 3, 4), (0, 8, 9)]),
|
||||||
|
t!(basic100, &["aa"], "", &[]),
|
||||||
|
t!(basic110, &["aa"], "aa", &[(0, 0, 2)]),
|
||||||
|
t!(basic120, &["aa"], "aabbaa", &[(0, 0, 2), (0, 4, 6)]),
|
||||||
|
t!(basic130, &["aa"], "abbab", &[]),
|
||||||
|
t!(basic140, &["aa"], "abbabaa", &[(0, 5, 7)]),
|
||||||
|
t!(basic150, &["aaa"], "aaa", &[(0, 0, 3)]),
|
||||||
|
t!(basic200, &["abc"], "abc", &[(0, 0, 3)]),
|
||||||
|
t!(basic210, &["abc"], "zazabzabcz", &[(0, 6, 9)]),
|
||||||
|
t!(basic220, &["abc"], "zazabczabcz", &[(0, 3, 6), (0, 7, 10)]),
|
||||||
|
t!(basic230, &["abcd"], "abcd", &[(0, 0, 4)]),
|
||||||
|
t!(basic240, &["abcd"], "zazabzabcdz", &[(0, 6, 10)]),
|
||||||
|
t!(basic250, &["abcd"], "zazabcdzabcdz", &[(0, 3, 7), (0, 8, 12)]),
|
||||||
|
t!(basic300, &["a", "b"], "", &[]),
|
||||||
|
t!(basic310, &["a", "b"], "z", &[]),
|
||||||
|
t!(basic320, &["a", "b"], "b", &[(1, 0, 1)]),
|
||||||
|
t!(basic330, &["a", "b"], "a", &[(0, 0, 1)]),
|
||||||
|
t!(
|
||||||
|
basic340,
|
||||||
|
&["a", "b"],
|
||||||
|
"abba",
|
||||||
|
&[(0, 0, 1), (1, 1, 2), (1, 2, 3), (0, 3, 4),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
basic350,
|
||||||
|
&["b", "a"],
|
||||||
|
"abba",
|
||||||
|
&[(1, 0, 1), (0, 1, 2), (0, 2, 3), (1, 3, 4),]
|
||||||
|
),
|
||||||
|
t!(basic360, &["abc", "bc"], "xbc", &[(1, 1, 3),]),
|
||||||
|
t!(basic400, &["foo", "bar"], "", &[]),
|
||||||
|
t!(basic410, &["foo", "bar"], "foobar", &[(0, 0, 3), (1, 3, 6),]),
|
||||||
|
t!(basic420, &["foo", "bar"], "barfoo", &[(1, 0, 3), (0, 3, 6),]),
|
||||||
|
t!(basic430, &["foo", "bar"], "foofoo", &[(0, 0, 3), (0, 3, 6),]),
|
||||||
|
t!(basic440, &["foo", "bar"], "barbar", &[(1, 0, 3), (1, 3, 6),]),
|
||||||
|
t!(basic450, &["foo", "bar"], "bafofoo", &[(0, 4, 7),]),
|
||||||
|
t!(basic460, &["bar", "foo"], "bafofoo", &[(1, 4, 7),]),
|
||||||
|
t!(basic470, &["foo", "bar"], "fobabar", &[(1, 4, 7),]),
|
||||||
|
t!(basic480, &["bar", "foo"], "fobabar", &[(0, 4, 7),]),
|
||||||
|
t!(basic700, &["yabcdef", "abcdezghi"], "yabcdefghi", &[(0, 0, 7),]),
|
||||||
|
t!(basic710, &["yabcdef", "abcdezghi"], "yabcdezghi", &[(1, 1, 10),]),
|
||||||
|
t!(
|
||||||
|
basic720,
|
||||||
|
&["yabcdef", "bcdeyabc", "abcdezghi"],
|
||||||
|
"yabcdezghi",
|
||||||
|
&[(2, 1, 10),]
|
||||||
|
),
|
||||||
|
t!(basic810, &["abcd", "bcd", "cd"], "abcd", &[(0, 0, 4),]),
|
||||||
|
t!(basic820, &["bcd", "cd", "abcd"], "abcd", &[(2, 0, 4),]),
|
||||||
|
t!(basic830, &["abc", "bc"], "zazabcz", &[(0, 3, 6),]),
|
||||||
|
t!(
|
||||||
|
basic840,
|
||||||
|
&["ab", "ba"],
|
||||||
|
"abababa",
|
||||||
|
&[(0, 0, 2), (0, 2, 4), (0, 4, 6),]
|
||||||
|
),
|
||||||
|
t!(basic850, &["foo", "foo"], "foobarfoo", &[(0, 0, 3), (0, 6, 9),]),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Tests for leftmost match semantics. These should pass for both
|
||||||
|
/// leftmost-first and leftmost-longest match kinds. Stated differently, among
|
||||||
|
/// ambiguous matches, the longest match and the match that appeared first when
|
||||||
|
/// constructing the automaton should always be the same.
|
||||||
|
const LEFTMOST: &'static [SearchTest] = &[
|
||||||
|
t!(leftmost000, &["ab", "ab"], "abcd", &[(0, 0, 2)]),
|
||||||
|
t!(leftmost030, &["a", "ab"], "aa", &[(0, 0, 1), (0, 1, 2)]),
|
||||||
|
t!(leftmost031, &["ab", "a"], "aa", &[(1, 0, 1), (1, 1, 2)]),
|
||||||
|
t!(leftmost032, &["ab", "a"], "xayabbbz", &[(1, 1, 2), (0, 3, 5)]),
|
||||||
|
t!(leftmost300, &["abcd", "bce", "b"], "abce", &[(1, 1, 4)]),
|
||||||
|
t!(leftmost310, &["abcd", "ce", "bc"], "abce", &[(2, 1, 3)]),
|
||||||
|
t!(leftmost320, &["abcd", "bce", "ce", "b"], "abce", &[(1, 1, 4)]),
|
||||||
|
t!(leftmost330, &["abcd", "bce", "cz", "bc"], "abcz", &[(3, 1, 3)]),
|
||||||
|
t!(leftmost340, &["bce", "cz", "bc"], "bcz", &[(2, 0, 2)]),
|
||||||
|
t!(leftmost350, &["abc", "bd", "ab"], "abd", &[(2, 0, 2)]),
|
||||||
|
t!(
|
||||||
|
leftmost360,
|
||||||
|
&["abcdefghi", "hz", "abcdefgh"],
|
||||||
|
"abcdefghz",
|
||||||
|
&[(2, 0, 8),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
leftmost370,
|
||||||
|
&["abcdefghi", "cde", "hz", "abcdefgh"],
|
||||||
|
"abcdefghz",
|
||||||
|
&[(3, 0, 8),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
leftmost380,
|
||||||
|
&["abcdefghi", "hz", "abcdefgh", "a"],
|
||||||
|
"abcdefghz",
|
||||||
|
&[(2, 0, 8),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
leftmost390,
|
||||||
|
&["b", "abcdefghi", "hz", "abcdefgh"],
|
||||||
|
"abcdefghz",
|
||||||
|
&[(3, 0, 8),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
leftmost400,
|
||||||
|
&["h", "abcdefghi", "hz", "abcdefgh"],
|
||||||
|
"abcdefghz",
|
||||||
|
&[(3, 0, 8),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
leftmost410,
|
||||||
|
&["z", "abcdefghi", "hz", "abcdefgh"],
|
||||||
|
"abcdefghz",
|
||||||
|
&[(3, 0, 8), (0, 8, 9),]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Tests for non-overlapping leftmost-first match semantics. These tests
|
||||||
|
/// should generally be specific to leftmost-first, which means they should
|
||||||
|
/// generally fail under leftmost-longest semantics.
|
||||||
|
const LEFTMOST_FIRST: &'static [SearchTest] = &[
|
||||||
|
t!(leftfirst000, &["ab", "abcd"], "abcd", &[(0, 0, 2)]),
|
||||||
|
t!(leftfirst020, &["abcd", "ab"], "abcd", &[(0, 0, 4)]),
|
||||||
|
t!(leftfirst030, &["ab", "ab"], "abcd", &[(0, 0, 2)]),
|
||||||
|
t!(leftfirst040, &["a", "ab"], "xayabbbz", &[(0, 1, 2), (0, 3, 4)]),
|
||||||
|
t!(leftfirst100, &["abcdefg", "bcde", "bcdef"], "abcdef", &[(1, 1, 5)]),
|
||||||
|
t!(leftfirst110, &["abcdefg", "bcdef", "bcde"], "abcdef", &[(1, 1, 6)]),
|
||||||
|
t!(leftfirst300, &["abcd", "b", "bce"], "abce", &[(1, 1, 2)]),
|
||||||
|
t!(
|
||||||
|
leftfirst310,
|
||||||
|
&["abcd", "b", "bce", "ce"],
|
||||||
|
"abce",
|
||||||
|
&[(1, 1, 2), (3, 2, 4),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
leftfirst320,
|
||||||
|
&["a", "abcdefghi", "hz", "abcdefgh"],
|
||||||
|
"abcdefghz",
|
||||||
|
&[(0, 0, 1), (2, 7, 9),]
|
||||||
|
),
|
||||||
|
t!(leftfirst330, &["a", "abab"], "abab", &[(0, 0, 1), (0, 2, 3)]),
|
||||||
|
t!(
|
||||||
|
leftfirst340,
|
||||||
|
&["abcdef", "x", "x", "x", "x", "x", "x", "abcde"],
|
||||||
|
"abcdef",
|
||||||
|
&[(0, 0, 6)]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Tests for non-overlapping leftmost-longest match semantics. These tests
|
||||||
|
/// should generally be specific to leftmost-longest, which means they should
|
||||||
|
/// generally fail under leftmost-first semantics.
|
||||||
|
const LEFTMOST_LONGEST: &'static [SearchTest] = &[
|
||||||
|
t!(leftlong000, &["ab", "abcd"], "abcd", &[(1, 0, 4)]),
|
||||||
|
t!(leftlong010, &["abcd", "bcd", "cd", "b"], "abcd", &[(0, 0, 4),]),
|
||||||
|
t!(leftlong040, &["a", "ab"], "a", &[(0, 0, 1)]),
|
||||||
|
t!(leftlong050, &["a", "ab"], "ab", &[(1, 0, 2)]),
|
||||||
|
t!(leftlong060, &["ab", "a"], "a", &[(1, 0, 1)]),
|
||||||
|
t!(leftlong070, &["ab", "a"], "ab", &[(0, 0, 2)]),
|
||||||
|
t!(leftlong100, &["abcdefg", "bcde", "bcdef"], "abcdef", &[(2, 1, 6)]),
|
||||||
|
t!(leftlong110, &["abcdefg", "bcdef", "bcde"], "abcdef", &[(1, 1, 6)]),
|
||||||
|
t!(leftlong300, &["abcd", "b", "bce"], "abce", &[(2, 1, 4)]),
|
||||||
|
t!(
|
||||||
|
leftlong310,
|
||||||
|
&["a", "abcdefghi", "hz", "abcdefgh"],
|
||||||
|
"abcdefghz",
|
||||||
|
&[(3, 0, 8),]
|
||||||
|
),
|
||||||
|
t!(leftlong320, &["a", "abab"], "abab", &[(1, 0, 4)]),
|
||||||
|
t!(leftlong330, &["abcd", "b", "ce"], "abce", &[(1, 1, 2), (2, 2, 4),]),
|
||||||
|
t!(leftlong340, &["a", "ab"], "xayabbbz", &[(0, 1, 2), (1, 3, 5)]),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Regression tests that are applied to all combinations.
|
||||||
|
///
|
||||||
|
/// If regression tests are needed for specific match semantics, then add them
|
||||||
|
/// to the appropriate group above.
|
||||||
|
const REGRESSION: &'static [SearchTest] = &[
|
||||||
|
t!(regression010, &["inf", "ind"], "infind", &[(0, 0, 3), (1, 3, 6),]),
|
||||||
|
t!(regression020, &["ind", "inf"], "infind", &[(1, 0, 3), (0, 3, 6),]),
|
||||||
|
t!(
|
||||||
|
regression030,
|
||||||
|
&["libcore/", "libstd/"],
|
||||||
|
"libcore/char/methods.rs",
|
||||||
|
&[(0, 0, 8),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
regression040,
|
||||||
|
&["libstd/", "libcore/"],
|
||||||
|
"libcore/char/methods.rs",
|
||||||
|
&[(1, 0, 8),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
regression050,
|
||||||
|
&["\x00\x00\x01", "\x00\x00\x00"],
|
||||||
|
"\x00\x00\x00",
|
||||||
|
&[(1, 0, 3),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
regression060,
|
||||||
|
&["\x00\x00\x00", "\x00\x00\x01"],
|
||||||
|
"\x00\x00\x00",
|
||||||
|
&[(0, 0, 3),]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const TEDDY: &'static [SearchTest] = &[
|
||||||
|
t!(
|
||||||
|
teddy010,
|
||||||
|
&["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"],
|
||||||
|
"abcdefghijk",
|
||||||
|
&[
|
||||||
|
(0, 0, 1),
|
||||||
|
(1, 1, 2),
|
||||||
|
(2, 2, 3),
|
||||||
|
(3, 3, 4),
|
||||||
|
(4, 4, 5),
|
||||||
|
(5, 5, 6),
|
||||||
|
(6, 6, 7),
|
||||||
|
(7, 7, 8),
|
||||||
|
(8, 8, 9),
|
||||||
|
(9, 9, 10),
|
||||||
|
(10, 10, 11)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
teddy020,
|
||||||
|
&["ab", "bc", "cd", "de", "ef", "fg", "gh", "hi", "ij", "jk", "kl"],
|
||||||
|
"abcdefghijk",
|
||||||
|
&[(0, 0, 2), (2, 2, 4), (4, 4, 6), (6, 6, 8), (8, 8, 10),]
|
||||||
|
),
|
||||||
|
t!(
|
||||||
|
teddy030,
|
||||||
|
&["abc"],
|
||||||
|
"abcdefghijklmnopqrstuvwxyzabcdefghijk",
|
||||||
|
&[(0, 0, 3), (0, 26, 29)]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Now define a test for each combination of things above that we want to run.
|
||||||
|
// Since there are a few different combinations for each collection of tests,
|
||||||
|
// we define a couple of macros to avoid repetition drudgery. The testconfig
|
||||||
|
// macro constructs the automaton from a given match kind, and runs the search
|
||||||
|
// tests one-by-one over the given collection. The `with` parameter allows one
|
||||||
|
// to configure the config with additional parameters. The testcombo macro
|
||||||
|
// invokes testconfig in precisely this way: it sets up several tests where
|
||||||
|
// each one turns a different knob on Config.
|
||||||
|
|
||||||
|
macro_rules! testconfig {
|
||||||
|
($name:ident, $collection:expr, $with:expr) => {
|
||||||
|
#[test]
|
||||||
|
fn $name() {
|
||||||
|
run_search_tests($collection, |test| {
|
||||||
|
let mut config = Config::new();
|
||||||
|
$with(&mut config);
|
||||||
|
let mut builder = config.builder();
|
||||||
|
builder.extend(test.patterns.iter().map(|p| p.as_bytes()));
|
||||||
|
let searcher = match builder.build() {
|
||||||
|
Some(searcher) => searcher,
|
||||||
|
None => {
|
||||||
|
// For x86-64 and aarch64, not building a searcher is
|
||||||
|
// probably a bug, so be loud.
|
||||||
|
if cfg!(any(
|
||||||
|
target_arch = "x86_64",
|
||||||
|
target_arch = "aarch64"
|
||||||
|
)) {
|
||||||
|
panic!("failed to build packed searcher")
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(searcher.find_iter(&test.haystack).collect())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_default_leftmost_first,
|
||||||
|
PACKED_LEFTMOST_FIRST,
|
||||||
|
|_: &mut Config| {}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_default_leftmost_longest,
|
||||||
|
PACKED_LEFTMOST_LONGEST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.match_kind(MatchKind::LeftmostLongest);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_teddy_leftmost_first,
|
||||||
|
PACKED_LEFTMOST_FIRST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.only_teddy(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_teddy_leftmost_longest,
|
||||||
|
PACKED_LEFTMOST_LONGEST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.only_teddy(true).match_kind(MatchKind::LeftmostLongest);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_teddy_ssse3_leftmost_first,
|
||||||
|
PACKED_LEFTMOST_FIRST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.only_teddy(true);
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
if std::is_x86_feature_detected!("ssse3") {
|
||||||
|
c.only_teddy_256bit(Some(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_teddy_ssse3_leftmost_longest,
|
||||||
|
PACKED_LEFTMOST_LONGEST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.only_teddy(true).match_kind(MatchKind::LeftmostLongest);
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
if std::is_x86_feature_detected!("ssse3") {
|
||||||
|
c.only_teddy_256bit(Some(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_teddy_avx2_leftmost_first,
|
||||||
|
PACKED_LEFTMOST_FIRST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.only_teddy(true);
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
if std::is_x86_feature_detected!("avx2") {
|
||||||
|
c.only_teddy_256bit(Some(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_teddy_avx2_leftmost_longest,
|
||||||
|
PACKED_LEFTMOST_LONGEST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.only_teddy(true).match_kind(MatchKind::LeftmostLongest);
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
if std::is_x86_feature_detected!("avx2") {
|
||||||
|
c.only_teddy_256bit(Some(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_teddy_fat_leftmost_first,
|
||||||
|
PACKED_LEFTMOST_FIRST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.only_teddy(true);
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
if std::is_x86_feature_detected!("avx2") {
|
||||||
|
c.only_teddy_fat(Some(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_teddy_fat_leftmost_longest,
|
||||||
|
PACKED_LEFTMOST_LONGEST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.only_teddy(true).match_kind(MatchKind::LeftmostLongest);
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
if std::is_x86_feature_detected!("avx2") {
|
||||||
|
c.only_teddy_fat(Some(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_rabinkarp_leftmost_first,
|
||||||
|
PACKED_LEFTMOST_FIRST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.only_rabin_karp(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
testconfig!(
|
||||||
|
search_rabinkarp_leftmost_longest,
|
||||||
|
PACKED_LEFTMOST_LONGEST,
|
||||||
|
|c: &mut Config| {
|
||||||
|
c.only_rabin_karp(true).match_kind(MatchKind::LeftmostLongest);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn search_tests_have_unique_names() {
|
||||||
|
let assert = |constname, tests: &[SearchTest]| {
|
||||||
|
let mut seen = HashMap::new(); // map from test name to position
|
||||||
|
for (i, test) in tests.iter().enumerate() {
|
||||||
|
if !seen.contains_key(test.name) {
|
||||||
|
seen.insert(test.name, i);
|
||||||
|
} else {
|
||||||
|
let last = seen[test.name];
|
||||||
|
panic!(
|
||||||
|
"{} tests have duplicate names at positions {} and {}",
|
||||||
|
constname, last, i
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert("BASICS", BASICS);
|
||||||
|
assert("LEFTMOST", LEFTMOST);
|
||||||
|
assert("LEFTMOST_FIRST", LEFTMOST_FIRST);
|
||||||
|
assert("LEFTMOST_LONGEST", LEFTMOST_LONGEST);
|
||||||
|
assert("REGRESSION", REGRESSION);
|
||||||
|
assert("TEDDY", TEDDY);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_search_tests<F: FnMut(&SearchTestOwned) -> Option<Vec<Match>>>(
|
||||||
|
which: TestCollection,
|
||||||
|
mut f: F,
|
||||||
|
) {
|
||||||
|
let get_match_triples =
|
||||||
|
|matches: Vec<Match>| -> Vec<(usize, usize, usize)> {
|
||||||
|
matches
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| (m.pattern().as_usize(), m.start(), m.end()))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
for &tests in which {
|
||||||
|
for spec in tests {
|
||||||
|
for test in spec.variations() {
|
||||||
|
let results = match f(&test) {
|
||||||
|
None => continue,
|
||||||
|
Some(results) => results,
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
test.matches,
|
||||||
|
get_match_triples(results).as_slice(),
|
||||||
|
"test: {}, patterns: {:?}, haystack(len={:?}): {:?}, \
|
||||||
|
offset: {:?}",
|
||||||
|
test.name,
|
||||||
|
test.patterns,
|
||||||
|
test.haystack.len(),
|
||||||
|
test.haystack,
|
||||||
|
test.offset,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1757
third_party/crates/aho-corasick-1.1.5/src/packed/vector.rs
vendored
Normal file
1757
third_party/crates/aho-corasick-1.1.5/src/packed/vector.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1664
third_party/crates/aho-corasick-1.1.5/src/tests.rs
vendored
Normal file
1664
third_party/crates/aho-corasick-1.1.5/src/tests.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
270
third_party/crates/aho-corasick-1.1.5/src/transducer.rs
vendored
Normal file
270
third_party/crates/aho-corasick-1.1.5/src/transducer.rs
vendored
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
/*!
|
||||||
|
Provides implementations of `fst::Automaton` for Aho-Corasick automata.
|
||||||
|
|
||||||
|
This works by providing two wrapper types, [`Anchored`] and [`Unanchored`].
|
||||||
|
The former executes an anchored search on an FST while the latter executes
|
||||||
|
an unanchored search. Building these wrappers is fallible and will fail if
|
||||||
|
the underlying Aho-Corasick automaton does not support the type of search it
|
||||||
|
represents.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
automaton::{Automaton, StateID},
|
||||||
|
Anchored as AcAnchored, Input, MatchError,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents an unanchored Aho-Corasick search of a finite state transducer.
|
||||||
|
///
|
||||||
|
/// Wrapping an Aho-Corasick automaton in `Unanchored` will fail if the
|
||||||
|
/// underlying automaton does not support unanchored searches.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This shows how to build an FST of keys and then run an unanchored search on
|
||||||
|
/// those keys using an Aho-Corasick automaton.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{nfa::contiguous::NFA, transducer::Unanchored};
|
||||||
|
/// use fst::{Automaton, IntoStreamer, Set, Streamer};
|
||||||
|
///
|
||||||
|
/// let set = Set::from_iter(&["abcd", "bc", "bcd", "xyz"]).unwrap();
|
||||||
|
/// let nfa = NFA::new(&["bcd", "x"]).unwrap();
|
||||||
|
/// // NFAs always support both unanchored and anchored searches.
|
||||||
|
/// let searcher = Unanchored::new(&nfa).unwrap();
|
||||||
|
///
|
||||||
|
/// let mut stream = set.search(searcher).into_stream();
|
||||||
|
/// let mut results = vec![];
|
||||||
|
/// while let Some(key) = stream.next() {
|
||||||
|
/// results.push(std::str::from_utf8(key).unwrap().to_string());
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(vec!["abcd", "bcd", "xyz"], results);
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Unanchored<A>(A);
|
||||||
|
|
||||||
|
impl<A: Automaton> Unanchored<A> {
|
||||||
|
/// Create a new `Unanchored` implementation of the `fst::Automaton` trait.
|
||||||
|
///
|
||||||
|
/// If the given Aho-Corasick automaton does not support unanchored
|
||||||
|
/// searches, then this returns an error.
|
||||||
|
pub fn new(aut: A) -> Result<Unanchored<A>, MatchError> {
|
||||||
|
let input = Input::new("").anchored(AcAnchored::No);
|
||||||
|
let _ = aut.start_state(&input)?;
|
||||||
|
Ok(Unanchored(aut))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a borrow to the underlying automaton.
|
||||||
|
pub fn as_ref(&self) -> &A {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unwrap this value and return the inner automaton.
|
||||||
|
pub fn into_inner(self) -> A {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Automaton> fst::Automaton for Unanchored<A> {
|
||||||
|
type State = StateID;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn start(&self) -> StateID {
|
||||||
|
let input = Input::new("").anchored(AcAnchored::No);
|
||||||
|
self.0.start_state(&input).expect("support for unanchored searches")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_match(&self, state: &StateID) -> bool {
|
||||||
|
self.0.is_match(*state)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn accept(&self, state: &StateID, byte: u8) -> StateID {
|
||||||
|
if fst::Automaton::is_match(self, state) {
|
||||||
|
return *state;
|
||||||
|
}
|
||||||
|
self.0.next_state(AcAnchored::No, *state, byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn can_match(&self, state: &StateID) -> bool {
|
||||||
|
!self.0.is_dead(*state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an anchored Aho-Corasick search of a finite state transducer.
|
||||||
|
///
|
||||||
|
/// Wrapping an Aho-Corasick automaton in `Unanchored` will fail if the
|
||||||
|
/// underlying automaton does not support unanchored searches.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// This shows how to build an FST of keys and then run an anchored search on
|
||||||
|
/// those keys using an Aho-Corasick automaton.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{nfa::contiguous::NFA, transducer::Anchored};
|
||||||
|
/// use fst::{Automaton, IntoStreamer, Set, Streamer};
|
||||||
|
///
|
||||||
|
/// let set = Set::from_iter(&["abcd", "bc", "bcd", "xyz"]).unwrap();
|
||||||
|
/// let nfa = NFA::new(&["bcd", "x"]).unwrap();
|
||||||
|
/// // NFAs always support both unanchored and anchored searches.
|
||||||
|
/// let searcher = Anchored::new(&nfa).unwrap();
|
||||||
|
///
|
||||||
|
/// let mut stream = set.search(searcher).into_stream();
|
||||||
|
/// let mut results = vec![];
|
||||||
|
/// while let Some(key) = stream.next() {
|
||||||
|
/// results.push(std::str::from_utf8(key).unwrap().to_string());
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(vec!["bcd", "xyz"], results);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This is like the example above, except we use an Aho-Corasick DFA, which
|
||||||
|
/// requires explicitly configuring it to support anchored searches. (NFAs
|
||||||
|
/// unconditionally support both unanchored and anchored searches.)
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use aho_corasick::{dfa::DFA, transducer::Anchored, StartKind};
|
||||||
|
/// use fst::{Automaton, IntoStreamer, Set, Streamer};
|
||||||
|
///
|
||||||
|
/// let set = Set::from_iter(&["abcd", "bc", "bcd", "xyz"]).unwrap();
|
||||||
|
/// let dfa = DFA::builder()
|
||||||
|
/// .start_kind(StartKind::Anchored)
|
||||||
|
/// .build(&["bcd", "x"])
|
||||||
|
/// .unwrap();
|
||||||
|
/// // We've explicitly configured our DFA to support anchored searches.
|
||||||
|
/// let searcher = Anchored::new(&dfa).unwrap();
|
||||||
|
///
|
||||||
|
/// let mut stream = set.search(searcher).into_stream();
|
||||||
|
/// let mut results = vec![];
|
||||||
|
/// while let Some(key) = stream.next() {
|
||||||
|
/// results.push(std::str::from_utf8(key).unwrap().to_string());
|
||||||
|
/// }
|
||||||
|
/// assert_eq!(vec!["bcd", "xyz"], results);
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Anchored<A>(A);
|
||||||
|
|
||||||
|
impl<A: Automaton> Anchored<A> {
|
||||||
|
/// Create a new `Anchored` implementation of the `fst::Automaton` trait.
|
||||||
|
///
|
||||||
|
/// If the given Aho-Corasick automaton does not support anchored searches,
|
||||||
|
/// then this returns an error.
|
||||||
|
pub fn new(aut: A) -> Result<Anchored<A>, MatchError> {
|
||||||
|
let input = Input::new("").anchored(AcAnchored::Yes);
|
||||||
|
let _ = aut.start_state(&input)?;
|
||||||
|
Ok(Anchored(aut))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a borrow to the underlying automaton.
|
||||||
|
pub fn as_ref(&self) -> &A {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unwrap this value and return the inner automaton.
|
||||||
|
pub fn into_inner(self) -> A {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Automaton> fst::Automaton for Anchored<A> {
|
||||||
|
type State = StateID;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn start(&self) -> StateID {
|
||||||
|
let input = Input::new("").anchored(AcAnchored::Yes);
|
||||||
|
self.0.start_state(&input).expect("support for unanchored searches")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_match(&self, state: &StateID) -> bool {
|
||||||
|
self.0.is_match(*state)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn accept(&self, state: &StateID, byte: u8) -> StateID {
|
||||||
|
if fst::Automaton::is_match(self, state) {
|
||||||
|
return *state;
|
||||||
|
}
|
||||||
|
self.0.next_state(AcAnchored::Yes, *state, byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn can_match(&self, state: &StateID) -> bool {
|
||||||
|
!self.0.is_dead(*state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use alloc::{string::String, vec, vec::Vec};
|
||||||
|
|
||||||
|
use fst::{Automaton, IntoStreamer, Set, Streamer};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
dfa::DFA,
|
||||||
|
nfa::{contiguous, noncontiguous},
|
||||||
|
StartKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn search<A: Automaton, D: AsRef<[u8]>>(
|
||||||
|
set: &Set<D>,
|
||||||
|
aut: A,
|
||||||
|
) -> Vec<String> {
|
||||||
|
let mut stream = set.search(aut).into_stream();
|
||||||
|
let mut results = vec![];
|
||||||
|
while let Some(key) = stream.next() {
|
||||||
|
results.push(String::from(core::str::from_utf8(key).unwrap()));
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unanchored() {
|
||||||
|
let set =
|
||||||
|
Set::from_iter(&["a", "bar", "baz", "wat", "xba", "xbax", "z"])
|
||||||
|
.unwrap();
|
||||||
|
let patterns = vec!["baz", "bax"];
|
||||||
|
let expected = vec!["baz", "xbax"];
|
||||||
|
|
||||||
|
let aut = Unanchored(noncontiguous::NFA::new(&patterns).unwrap());
|
||||||
|
let got = search(&set, &aut);
|
||||||
|
assert_eq!(got, expected);
|
||||||
|
|
||||||
|
let aut = Unanchored(contiguous::NFA::new(&patterns).unwrap());
|
||||||
|
let got = search(&set, &aut);
|
||||||
|
assert_eq!(got, expected);
|
||||||
|
|
||||||
|
let aut = Unanchored(DFA::new(&patterns).unwrap());
|
||||||
|
let got = search(&set, &aut);
|
||||||
|
assert_eq!(got, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn anchored() {
|
||||||
|
let set =
|
||||||
|
Set::from_iter(&["a", "bar", "baz", "wat", "xba", "xbax", "z"])
|
||||||
|
.unwrap();
|
||||||
|
let patterns = vec!["baz", "bax"];
|
||||||
|
let expected = vec!["baz"];
|
||||||
|
|
||||||
|
let aut = Anchored(noncontiguous::NFA::new(&patterns).unwrap());
|
||||||
|
let got = search(&set, &aut);
|
||||||
|
assert_eq!(got, expected);
|
||||||
|
|
||||||
|
let aut = Anchored(contiguous::NFA::new(&patterns).unwrap());
|
||||||
|
let got = search(&set, &aut);
|
||||||
|
assert_eq!(got, expected);
|
||||||
|
|
||||||
|
let aut = Anchored(
|
||||||
|
DFA::builder()
|
||||||
|
.start_kind(StartKind::Anchored)
|
||||||
|
.build(&patterns)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let got = search(&set, &aut);
|
||||||
|
assert_eq!(got, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
409
third_party/crates/aho-corasick-1.1.5/src/util/alphabet.rs
vendored
Normal file
409
third_party/crates/aho-corasick-1.1.5/src/util/alphabet.rs
vendored
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
use crate::util::int::Usize;
|
||||||
|
|
||||||
|
/// A representation of byte oriented equivalence classes.
|
||||||
|
///
|
||||||
|
/// This is used in finite state machines to reduce the size of the transition
|
||||||
|
/// table. This can have a particularly large impact not only on the total size
|
||||||
|
/// of an FSM, but also on FSM build times because it reduces the number of
|
||||||
|
/// transitions that need to be visited/set.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) struct ByteClasses([u8; 256]);
|
||||||
|
|
||||||
|
impl ByteClasses {
|
||||||
|
/// Creates a new set of equivalence classes where all bytes are mapped to
|
||||||
|
/// the same class.
|
||||||
|
pub(crate) fn empty() -> ByteClasses {
|
||||||
|
ByteClasses([0; 256])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new set of equivalence classes where each byte belongs to
|
||||||
|
/// its own equivalence class.
|
||||||
|
pub(crate) fn singletons() -> ByteClasses {
|
||||||
|
let mut classes = ByteClasses::empty();
|
||||||
|
for b in 0..=255 {
|
||||||
|
classes.set(b, b);
|
||||||
|
}
|
||||||
|
classes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the equivalence class for the given byte.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set(&mut self, byte: u8, class: u8) {
|
||||||
|
self.0[usize::from(byte)] = class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the equivalence class for the given byte.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn get(&self, byte: u8) -> u8 {
|
||||||
|
self.0[usize::from(byte)]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the total number of elements in the alphabet represented by
|
||||||
|
/// these equivalence classes. Equivalently, this returns the total number
|
||||||
|
/// of equivalence classes.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn alphabet_len(&self) -> usize {
|
||||||
|
// Add one since the number of equivalence classes is one bigger than
|
||||||
|
// the last one.
|
||||||
|
usize::from(self.0[255]) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the stride, as a base-2 exponent, required for these
|
||||||
|
/// equivalence classes.
|
||||||
|
///
|
||||||
|
/// The stride is always the smallest power of 2 that is greater than or
|
||||||
|
/// equal to the alphabet length. This is done so that converting between
|
||||||
|
/// state IDs and indices can be done with shifts alone, which is much
|
||||||
|
/// faster than integer division. The "stride2" is the exponent. i.e.,
|
||||||
|
/// `2^stride2 = stride`.
|
||||||
|
pub(crate) fn stride2(&self) -> usize {
|
||||||
|
let zeros = self.alphabet_len().next_power_of_two().trailing_zeros();
|
||||||
|
usize::try_from(zeros).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the stride for these equivalence classes, which corresponds
|
||||||
|
/// to the smallest power of 2 greater than or equal to the number of
|
||||||
|
/// equivalence classes.
|
||||||
|
pub(crate) fn stride(&self) -> usize {
|
||||||
|
1 << self.stride2()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if every byte in this class maps to its own
|
||||||
|
/// equivalence class. Equivalently, there are 257 equivalence classes
|
||||||
|
/// and each class contains exactly one byte (plus the special EOI class).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn is_singleton(&self) -> bool {
|
||||||
|
self.alphabet_len() == 256
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all equivalence classes in this set.
|
||||||
|
pub(crate) fn iter(&self) -> ByteClassIter {
|
||||||
|
ByteClassIter { it: 0..self.alphabet_len() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator of the bytes in the given equivalence class.
|
||||||
|
pub(crate) fn elements(&self, class: u8) -> ByteClassElements {
|
||||||
|
ByteClassElements { classes: self, class, bytes: 0..=255 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator of byte ranges in the given equivalence class.
|
||||||
|
///
|
||||||
|
/// That is, a sequence of contiguous ranges are returned. Typically, every
|
||||||
|
/// class maps to a single contiguous range.
|
||||||
|
fn element_ranges(&self, class: u8) -> ByteClassElementRanges {
|
||||||
|
ByteClassElementRanges { elements: self.elements(class), range: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for ByteClasses {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
if self.is_singleton() {
|
||||||
|
write!(f, "ByteClasses(<one-class-per-byte>)")
|
||||||
|
} else {
|
||||||
|
write!(f, "ByteClasses(")?;
|
||||||
|
for (i, class) in self.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{:?} => [", class)?;
|
||||||
|
for (start, end) in self.element_ranges(class) {
|
||||||
|
if start == end {
|
||||||
|
write!(f, "{:?}", start)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{:?}-{:?}", start, end)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "]")?;
|
||||||
|
}
|
||||||
|
write!(f, ")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over each equivalence class.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ByteClassIter {
|
||||||
|
it: core::ops::Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ByteClassIter {
|
||||||
|
type Item = u8;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<u8> {
|
||||||
|
self.it.next().map(|class| class.as_u8())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over all elements in a specific equivalence class.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ByteClassElements<'a> {
|
||||||
|
classes: &'a ByteClasses,
|
||||||
|
class: u8,
|
||||||
|
bytes: core::ops::RangeInclusive<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for ByteClassElements<'a> {
|
||||||
|
type Item = u8;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<u8> {
|
||||||
|
while let Some(byte) = self.bytes.next() {
|
||||||
|
if self.class == self.classes.get(byte) {
|
||||||
|
return Some(byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over all elements in an equivalence class expressed as a
|
||||||
|
/// sequence of contiguous ranges.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ByteClassElementRanges<'a> {
|
||||||
|
elements: ByteClassElements<'a>,
|
||||||
|
range: Option<(u8, u8)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for ByteClassElementRanges<'a> {
|
||||||
|
type Item = (u8, u8);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(u8, u8)> {
|
||||||
|
loop {
|
||||||
|
let element = match self.elements.next() {
|
||||||
|
None => return self.range.take(),
|
||||||
|
Some(element) => element,
|
||||||
|
};
|
||||||
|
match self.range.take() {
|
||||||
|
None => {
|
||||||
|
self.range = Some((element, element));
|
||||||
|
}
|
||||||
|
Some((start, end)) => {
|
||||||
|
if usize::from(end) + 1 != usize::from(element) {
|
||||||
|
self.range = Some((element, element));
|
||||||
|
return Some((start, end));
|
||||||
|
}
|
||||||
|
self.range = Some((start, element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A partitioning of bytes into equivalence classes.
|
||||||
|
///
|
||||||
|
/// A byte class set keeps track of an *approximation* of equivalence classes
|
||||||
|
/// of bytes during NFA construction. That is, every byte in an equivalence
|
||||||
|
/// class cannot discriminate between a match and a non-match.
|
||||||
|
///
|
||||||
|
/// Note that this may not compute the minimal set of equivalence classes.
|
||||||
|
/// Basically, any byte in a pattern given to the noncontiguous NFA builder
|
||||||
|
/// will automatically be treated as its own equivalence class. All other
|
||||||
|
/// bytes---any byte not in any pattern---will be treated as their own
|
||||||
|
/// equivalence classes. In theory, all bytes not in any pattern should
|
||||||
|
/// be part of a single equivalence class, but in practice, we only treat
|
||||||
|
/// contiguous ranges of bytes as an equivalence class. So the number of
|
||||||
|
/// classes computed may be bigger than necessary. This usually doesn't make
|
||||||
|
/// much of a difference, and keeps the implementation simple.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct ByteClassSet(ByteSet);
|
||||||
|
|
||||||
|
impl Default for ByteClassSet {
|
||||||
|
fn default() -> ByteClassSet {
|
||||||
|
ByteClassSet::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ByteClassSet {
|
||||||
|
/// Create a new set of byte classes where all bytes are part of the same
|
||||||
|
/// equivalence class.
|
||||||
|
pub(crate) fn empty() -> Self {
|
||||||
|
ByteClassSet(ByteSet::empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicate the the range of byte given (inclusive) can discriminate a
|
||||||
|
/// match between it and all other bytes outside of the range.
|
||||||
|
pub(crate) fn set_range(&mut self, start: u8, end: u8) {
|
||||||
|
debug_assert!(start <= end);
|
||||||
|
if start > 0 {
|
||||||
|
self.0.add(start - 1);
|
||||||
|
}
|
||||||
|
self.0.add(end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert this boolean set to a map that maps all byte values to their
|
||||||
|
/// corresponding equivalence class. The last mapping indicates the largest
|
||||||
|
/// equivalence class identifier (which is never bigger than 255).
|
||||||
|
pub(crate) fn byte_classes(&self) -> ByteClasses {
|
||||||
|
let mut classes = ByteClasses::empty();
|
||||||
|
let mut class = 0u8;
|
||||||
|
let mut b = 0u8;
|
||||||
|
loop {
|
||||||
|
classes.set(b, class);
|
||||||
|
if b == 255 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if self.0.contains(b) {
|
||||||
|
class = class.checked_add(1).unwrap();
|
||||||
|
}
|
||||||
|
b = b.checked_add(1).unwrap();
|
||||||
|
}
|
||||||
|
classes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple set of bytes that is reasonably cheap to copy and allocation free.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub(crate) struct ByteSet {
|
||||||
|
bits: BitSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The representation of a byte set. Split out so that we can define a
|
||||||
|
/// convenient Debug impl for it while keeping "ByteSet" in the output.
|
||||||
|
#[derive(Clone, Copy, Default, Eq, PartialEq)]
|
||||||
|
struct BitSet([u128; 2]);
|
||||||
|
|
||||||
|
impl ByteSet {
|
||||||
|
/// Create an empty set of bytes.
|
||||||
|
pub(crate) fn empty() -> ByteSet {
|
||||||
|
ByteSet { bits: BitSet([0; 2]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a byte to this set.
|
||||||
|
///
|
||||||
|
/// If the given byte already belongs to this set, then this is a no-op.
|
||||||
|
pub(crate) fn add(&mut self, byte: u8) {
|
||||||
|
let bucket = byte / 128;
|
||||||
|
let bit = byte % 128;
|
||||||
|
self.bits.0[usize::from(bucket)] |= 1 << bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return true if and only if the given byte is in this set.
|
||||||
|
pub(crate) fn contains(&self, byte: u8) -> bool {
|
||||||
|
let bucket = byte / 128;
|
||||||
|
let bit = byte % 128;
|
||||||
|
self.bits.0[usize::from(bucket)] & (1 << bit) > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for BitSet {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
let mut fmtd = f.debug_set();
|
||||||
|
for b in 0u8..=255 {
|
||||||
|
if (ByteSet { bits: *self }).contains(b) {
|
||||||
|
fmtd.entry(&b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmtd.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use alloc::{vec, vec::Vec};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn byte_classes() {
|
||||||
|
let mut set = ByteClassSet::empty();
|
||||||
|
set.set_range(b'a', b'z');
|
||||||
|
|
||||||
|
let classes = set.byte_classes();
|
||||||
|
assert_eq!(classes.get(0), 0);
|
||||||
|
assert_eq!(classes.get(1), 0);
|
||||||
|
assert_eq!(classes.get(2), 0);
|
||||||
|
assert_eq!(classes.get(b'a' - 1), 0);
|
||||||
|
assert_eq!(classes.get(b'a'), 1);
|
||||||
|
assert_eq!(classes.get(b'm'), 1);
|
||||||
|
assert_eq!(classes.get(b'z'), 1);
|
||||||
|
assert_eq!(classes.get(b'z' + 1), 2);
|
||||||
|
assert_eq!(classes.get(254), 2);
|
||||||
|
assert_eq!(classes.get(255), 2);
|
||||||
|
|
||||||
|
let mut set = ByteClassSet::empty();
|
||||||
|
set.set_range(0, 2);
|
||||||
|
set.set_range(4, 6);
|
||||||
|
let classes = set.byte_classes();
|
||||||
|
assert_eq!(classes.get(0), 0);
|
||||||
|
assert_eq!(classes.get(1), 0);
|
||||||
|
assert_eq!(classes.get(2), 0);
|
||||||
|
assert_eq!(classes.get(3), 1);
|
||||||
|
assert_eq!(classes.get(4), 2);
|
||||||
|
assert_eq!(classes.get(5), 2);
|
||||||
|
assert_eq!(classes.get(6), 2);
|
||||||
|
assert_eq!(classes.get(7), 3);
|
||||||
|
assert_eq!(classes.get(255), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_byte_classes() {
|
||||||
|
let mut set = ByteClassSet::empty();
|
||||||
|
for b in 0u8..=255 {
|
||||||
|
set.set_range(b, b);
|
||||||
|
}
|
||||||
|
assert_eq!(set.byte_classes().alphabet_len(), 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn elements_typical() {
|
||||||
|
let mut set = ByteClassSet::empty();
|
||||||
|
set.set_range(b'b', b'd');
|
||||||
|
set.set_range(b'g', b'm');
|
||||||
|
set.set_range(b'z', b'z');
|
||||||
|
let classes = set.byte_classes();
|
||||||
|
// class 0: \x00-a
|
||||||
|
// class 1: b-d
|
||||||
|
// class 2: e-f
|
||||||
|
// class 3: g-m
|
||||||
|
// class 4: n-y
|
||||||
|
// class 5: z-z
|
||||||
|
// class 6: \x7B-\xFF
|
||||||
|
assert_eq!(classes.alphabet_len(), 7);
|
||||||
|
|
||||||
|
let elements = classes.elements(0).collect::<Vec<_>>();
|
||||||
|
assert_eq!(elements.len(), 98);
|
||||||
|
assert_eq!(elements[0], b'\x00');
|
||||||
|
assert_eq!(elements[97], b'a');
|
||||||
|
|
||||||
|
let elements = classes.elements(1).collect::<Vec<_>>();
|
||||||
|
assert_eq!(elements, vec![b'b', b'c', b'd'],);
|
||||||
|
|
||||||
|
let elements = classes.elements(2).collect::<Vec<_>>();
|
||||||
|
assert_eq!(elements, vec![b'e', b'f'],);
|
||||||
|
|
||||||
|
let elements = classes.elements(3).collect::<Vec<_>>();
|
||||||
|
assert_eq!(elements, vec![b'g', b'h', b'i', b'j', b'k', b'l', b'm',],);
|
||||||
|
|
||||||
|
let elements = classes.elements(4).collect::<Vec<_>>();
|
||||||
|
assert_eq!(elements.len(), 12);
|
||||||
|
assert_eq!(elements[0], b'n');
|
||||||
|
assert_eq!(elements[11], b'y');
|
||||||
|
|
||||||
|
let elements = classes.elements(5).collect::<Vec<_>>();
|
||||||
|
assert_eq!(elements, vec![b'z']);
|
||||||
|
|
||||||
|
let elements = classes.elements(6).collect::<Vec<_>>();
|
||||||
|
assert_eq!(elements.len(), 133);
|
||||||
|
assert_eq!(elements[0], b'\x7B');
|
||||||
|
assert_eq!(elements[132], b'\xFF');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn elements_singletons() {
|
||||||
|
let classes = ByteClasses::singletons();
|
||||||
|
assert_eq!(classes.alphabet_len(), 256);
|
||||||
|
|
||||||
|
let elements = classes.elements(b'a').collect::<Vec<_>>();
|
||||||
|
assert_eq!(elements, vec![b'a']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn elements_empty() {
|
||||||
|
let classes = ByteClasses::empty();
|
||||||
|
assert_eq!(classes.alphabet_len(), 1);
|
||||||
|
|
||||||
|
let elements = classes.elements(0).collect::<Vec<_>>();
|
||||||
|
assert_eq!(elements.len(), 256);
|
||||||
|
assert_eq!(elements[0], b'\x00');
|
||||||
|
assert_eq!(elements[255], b'\xFF');
|
||||||
|
}
|
||||||
|
}
|
||||||
124
third_party/crates/aho-corasick-1.1.5/src/util/buffer.rs
vendored
Normal file
124
third_party/crates/aho-corasick-1.1.5/src/util/buffer.rs
vendored
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
use alloc::{vec, vec::Vec};
|
||||||
|
|
||||||
|
/// The default buffer capacity that we use for the stream buffer.
|
||||||
|
const DEFAULT_BUFFER_CAPACITY: usize = 64 * (1 << 10); // 64 KB
|
||||||
|
|
||||||
|
/// A fairly simple roll buffer for supporting stream searches.
|
||||||
|
///
|
||||||
|
/// This buffer acts as a temporary place to store a fixed amount of data when
|
||||||
|
/// reading from a stream. Its central purpose is to allow "rolling" some
|
||||||
|
/// suffix of the data to the beginning of the buffer before refilling it with
|
||||||
|
/// more data from the stream. For example, let's say we are trying to match
|
||||||
|
/// "foobar" on a stream. When we report the match, we'd like to not only
|
||||||
|
/// report the correct offsets at which the match occurs, but also the matching
|
||||||
|
/// bytes themselves. So let's say our stream is a file with the following
|
||||||
|
/// contents: `test test foobar test test`. Now assume that we happen to read
|
||||||
|
/// the aforementioned file in two chunks: `test test foo` and `bar test test`.
|
||||||
|
/// Naively, it would not be possible to report a single contiguous `foobar`
|
||||||
|
/// match, but this roll buffer allows us to do that. Namely, after the second
|
||||||
|
/// read, the contents of the buffer should be `st foobar test test`, where the
|
||||||
|
/// search should ultimately resume immediately after `foo`. (The prefix `st `
|
||||||
|
/// is included because the roll buffer saves N bytes at the end of the buffer,
|
||||||
|
/// where N is the maximum possible length of a match.)
|
||||||
|
///
|
||||||
|
/// A lot of the logic for dealing with this is unfortunately split out between
|
||||||
|
/// this roll buffer and the `StreamChunkIter`.
|
||||||
|
///
|
||||||
|
/// Note also that this buffer is not actually required to just report matches.
|
||||||
|
/// Because a `Match` is just some offsets. But it *is* required for supporting
|
||||||
|
/// things like `try_stream_replace_all` because that needs some mechanism for
|
||||||
|
/// knowing which bytes in the stream correspond to a match and which don't. So
|
||||||
|
/// when a match occurs across two `read` calls, *something* needs to retain
|
||||||
|
/// the bytes from the previous `read` call because you don't know before the
|
||||||
|
/// second read call whether a match exists or not.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Buffer {
|
||||||
|
/// The raw buffer contents. This has a fixed size and never increases.
|
||||||
|
buf: Vec<u8>,
|
||||||
|
/// The minimum size of the buffer, which is equivalent to the maximum
|
||||||
|
/// possible length of a match. This corresponds to the amount that we
|
||||||
|
/// roll
|
||||||
|
min: usize,
|
||||||
|
/// The end of the contents of this buffer.
|
||||||
|
end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Buffer {
|
||||||
|
/// Create a new buffer for stream searching. The minimum buffer length
|
||||||
|
/// given should be the size of the maximum possible match length.
|
||||||
|
pub(crate) fn new(min_buffer_len: usize) -> Buffer {
|
||||||
|
let min = core::cmp::max(1, min_buffer_len);
|
||||||
|
// The minimum buffer amount is also the amount that we roll our
|
||||||
|
// buffer in order to support incremental searching. To this end,
|
||||||
|
// our actual capacity needs to be at least 1 byte bigger than our
|
||||||
|
// minimum amount, otherwise we won't have any overlap. In actuality,
|
||||||
|
// we want our buffer to be a bit bigger than that for performance
|
||||||
|
// reasons, so we set a lower bound of `8 * min`.
|
||||||
|
//
|
||||||
|
// TODO: It would be good to find a way to test the streaming
|
||||||
|
// implementation with the minimal buffer size. For now, we just
|
||||||
|
// uncomment out the next line and comment out the subsequent line.
|
||||||
|
// let capacity = 1 + min;
|
||||||
|
let capacity = core::cmp::max(min * 8, DEFAULT_BUFFER_CAPACITY);
|
||||||
|
Buffer { buf: vec![0; capacity], min, end: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the contents of this buffer.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn buffer(&self) -> &[u8] {
|
||||||
|
&self.buf[..self.end]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the minimum size of the buffer. The only way a buffer may be
|
||||||
|
/// smaller than this is if the stream itself contains less than the
|
||||||
|
/// minimum buffer amount.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn min_buffer_len(&self) -> usize {
|
||||||
|
self.min
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return all free capacity in this buffer.
|
||||||
|
fn free_buffer(&mut self) -> &mut [u8] {
|
||||||
|
&mut self.buf[self.end..]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refill the contents of this buffer by reading as much as possible into
|
||||||
|
/// this buffer's free capacity. If no more bytes could be read, then this
|
||||||
|
/// returns false. Otherwise, this reads until it has filled the buffer
|
||||||
|
/// past the minimum amount.
|
||||||
|
pub(crate) fn fill<R: std::io::Read>(
|
||||||
|
&mut self,
|
||||||
|
mut rdr: R,
|
||||||
|
) -> std::io::Result<bool> {
|
||||||
|
let mut readany = false;
|
||||||
|
loop {
|
||||||
|
let readlen = rdr.read(self.free_buffer())?;
|
||||||
|
if readlen == 0 {
|
||||||
|
return Ok(readany);
|
||||||
|
}
|
||||||
|
readany = true;
|
||||||
|
self.end += readlen;
|
||||||
|
if self.buffer().len() >= self.min {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Roll the contents of the buffer so that the suffix of this buffer is
|
||||||
|
/// moved to the front and all other contents are dropped. The size of the
|
||||||
|
/// suffix corresponds precisely to the minimum buffer length.
|
||||||
|
///
|
||||||
|
/// This should only be called when the entire contents of this buffer have
|
||||||
|
/// been searched.
|
||||||
|
pub(crate) fn roll(&mut self) {
|
||||||
|
let roll_start = self
|
||||||
|
.end
|
||||||
|
.checked_sub(self.min)
|
||||||
|
.expect("buffer capacity should be bigger than minimum amount");
|
||||||
|
let roll_end = roll_start + self.min;
|
||||||
|
|
||||||
|
assert!(roll_end <= self.end);
|
||||||
|
self.buf.copy_within(roll_start..roll_end, 0);
|
||||||
|
self.end = self.min;
|
||||||
|
}
|
||||||
|
}
|
||||||
258
third_party/crates/aho-corasick-1.1.5/src/util/byte_frequencies.rs
vendored
Normal file
258
third_party/crates/aho-corasick-1.1.5/src/util/byte_frequencies.rs
vendored
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
pub const BYTE_FREQUENCIES: [u8; 256] = [
|
||||||
|
55, // '\x00'
|
||||||
|
52, // '\x01'
|
||||||
|
51, // '\x02'
|
||||||
|
50, // '\x03'
|
||||||
|
49, // '\x04'
|
||||||
|
48, // '\x05'
|
||||||
|
47, // '\x06'
|
||||||
|
46, // '\x07'
|
||||||
|
45, // '\x08'
|
||||||
|
103, // '\t'
|
||||||
|
242, // '\n'
|
||||||
|
66, // '\x0b'
|
||||||
|
67, // '\x0c'
|
||||||
|
229, // '\r'
|
||||||
|
44, // '\x0e'
|
||||||
|
43, // '\x0f'
|
||||||
|
42, // '\x10'
|
||||||
|
41, // '\x11'
|
||||||
|
40, // '\x12'
|
||||||
|
39, // '\x13'
|
||||||
|
38, // '\x14'
|
||||||
|
37, // '\x15'
|
||||||
|
36, // '\x16'
|
||||||
|
35, // '\x17'
|
||||||
|
34, // '\x18'
|
||||||
|
33, // '\x19'
|
||||||
|
56, // '\x1a'
|
||||||
|
32, // '\x1b'
|
||||||
|
31, // '\x1c'
|
||||||
|
30, // '\x1d'
|
||||||
|
29, // '\x1e'
|
||||||
|
28, // '\x1f'
|
||||||
|
255, // ' '
|
||||||
|
148, // '!'
|
||||||
|
164, // '"'
|
||||||
|
149, // '#'
|
||||||
|
136, // '$'
|
||||||
|
160, // '%'
|
||||||
|
155, // '&'
|
||||||
|
173, // "'"
|
||||||
|
221, // '('
|
||||||
|
222, // ')'
|
||||||
|
134, // '*'
|
||||||
|
122, // '+'
|
||||||
|
232, // ','
|
||||||
|
202, // '-'
|
||||||
|
215, // '.'
|
||||||
|
224, // '/'
|
||||||
|
208, // '0'
|
||||||
|
220, // '1'
|
||||||
|
204, // '2'
|
||||||
|
187, // '3'
|
||||||
|
183, // '4'
|
||||||
|
179, // '5'
|
||||||
|
177, // '6'
|
||||||
|
168, // '7'
|
||||||
|
178, // '8'
|
||||||
|
200, // '9'
|
||||||
|
226, // ':'
|
||||||
|
195, // ';'
|
||||||
|
154, // '<'
|
||||||
|
184, // '='
|
||||||
|
174, // '>'
|
||||||
|
126, // '?'
|
||||||
|
120, // '@'
|
||||||
|
191, // 'A'
|
||||||
|
157, // 'B'
|
||||||
|
194, // 'C'
|
||||||
|
170, // 'D'
|
||||||
|
189, // 'E'
|
||||||
|
162, // 'F'
|
||||||
|
161, // 'G'
|
||||||
|
150, // 'H'
|
||||||
|
193, // 'I'
|
||||||
|
142, // 'J'
|
||||||
|
137, // 'K'
|
||||||
|
171, // 'L'
|
||||||
|
176, // 'M'
|
||||||
|
185, // 'N'
|
||||||
|
167, // 'O'
|
||||||
|
186, // 'P'
|
||||||
|
112, // 'Q'
|
||||||
|
175, // 'R'
|
||||||
|
192, // 'S'
|
||||||
|
188, // 'T'
|
||||||
|
156, // 'U'
|
||||||
|
140, // 'V'
|
||||||
|
143, // 'W'
|
||||||
|
123, // 'X'
|
||||||
|
133, // 'Y'
|
||||||
|
128, // 'Z'
|
||||||
|
147, // '['
|
||||||
|
138, // '\\'
|
||||||
|
146, // ']'
|
||||||
|
114, // '^'
|
||||||
|
223, // '_'
|
||||||
|
151, // '`'
|
||||||
|
249, // 'a'
|
||||||
|
216, // 'b'
|
||||||
|
238, // 'c'
|
||||||
|
236, // 'd'
|
||||||
|
253, // 'e'
|
||||||
|
227, // 'f'
|
||||||
|
218, // 'g'
|
||||||
|
230, // 'h'
|
||||||
|
247, // 'i'
|
||||||
|
135, // 'j'
|
||||||
|
180, // 'k'
|
||||||
|
241, // 'l'
|
||||||
|
233, // 'm'
|
||||||
|
246, // 'n'
|
||||||
|
244, // 'o'
|
||||||
|
231, // 'p'
|
||||||
|
139, // 'q'
|
||||||
|
245, // 'r'
|
||||||
|
243, // 's'
|
||||||
|
251, // 't'
|
||||||
|
235, // 'u'
|
||||||
|
201, // 'v'
|
||||||
|
196, // 'w'
|
||||||
|
240, // 'x'
|
||||||
|
214, // 'y'
|
||||||
|
152, // 'z'
|
||||||
|
182, // '{'
|
||||||
|
205, // '|'
|
||||||
|
181, // '}'
|
||||||
|
127, // '~'
|
||||||
|
27, // '\x7f'
|
||||||
|
212, // '\x80'
|
||||||
|
211, // '\x81'
|
||||||
|
210, // '\x82'
|
||||||
|
213, // '\x83'
|
||||||
|
228, // '\x84'
|
||||||
|
197, // '\x85'
|
||||||
|
169, // '\x86'
|
||||||
|
159, // '\x87'
|
||||||
|
131, // '\x88'
|
||||||
|
172, // '\x89'
|
||||||
|
105, // '\x8a'
|
||||||
|
80, // '\x8b'
|
||||||
|
98, // '\x8c'
|
||||||
|
96, // '\x8d'
|
||||||
|
97, // '\x8e'
|
||||||
|
81, // '\x8f'
|
||||||
|
207, // '\x90'
|
||||||
|
145, // '\x91'
|
||||||
|
116, // '\x92'
|
||||||
|
115, // '\x93'
|
||||||
|
144, // '\x94'
|
||||||
|
130, // '\x95'
|
||||||
|
153, // '\x96'
|
||||||
|
121, // '\x97'
|
||||||
|
107, // '\x98'
|
||||||
|
132, // '\x99'
|
||||||
|
109, // '\x9a'
|
||||||
|
110, // '\x9b'
|
||||||
|
124, // '\x9c'
|
||||||
|
111, // '\x9d'
|
||||||
|
82, // '\x9e'
|
||||||
|
108, // '\x9f'
|
||||||
|
118, // '\xa0'
|
||||||
|
141, // '¡'
|
||||||
|
113, // '¢'
|
||||||
|
129, // '£'
|
||||||
|
119, // '¤'
|
||||||
|
125, // '¥'
|
||||||
|
165, // '¦'
|
||||||
|
117, // '§'
|
||||||
|
92, // '¨'
|
||||||
|
106, // '©'
|
||||||
|
83, // 'ª'
|
||||||
|
72, // '«'
|
||||||
|
99, // '¬'
|
||||||
|
93, // '\xad'
|
||||||
|
65, // '®'
|
||||||
|
79, // '¯'
|
||||||
|
166, // '°'
|
||||||
|
237, // '±'
|
||||||
|
163, // '²'
|
||||||
|
199, // '³'
|
||||||
|
190, // '´'
|
||||||
|
225, // 'µ'
|
||||||
|
209, // '¶'
|
||||||
|
203, // '·'
|
||||||
|
198, // '¸'
|
||||||
|
217, // '¹'
|
||||||
|
219, // 'º'
|
||||||
|
206, // '»'
|
||||||
|
234, // '¼'
|
||||||
|
248, // '½'
|
||||||
|
158, // '¾'
|
||||||
|
239, // '¿'
|
||||||
|
255, // 'À'
|
||||||
|
255, // 'Á'
|
||||||
|
255, // 'Â'
|
||||||
|
255, // 'Ã'
|
||||||
|
255, // 'Ä'
|
||||||
|
255, // 'Å'
|
||||||
|
255, // 'Æ'
|
||||||
|
255, // 'Ç'
|
||||||
|
255, // 'È'
|
||||||
|
255, // 'É'
|
||||||
|
255, // 'Ê'
|
||||||
|
255, // 'Ë'
|
||||||
|
255, // 'Ì'
|
||||||
|
255, // 'Í'
|
||||||
|
255, // 'Î'
|
||||||
|
255, // 'Ï'
|
||||||
|
255, // 'Ð'
|
||||||
|
255, // 'Ñ'
|
||||||
|
255, // 'Ò'
|
||||||
|
255, // 'Ó'
|
||||||
|
255, // 'Ô'
|
||||||
|
255, // 'Õ'
|
||||||
|
255, // 'Ö'
|
||||||
|
255, // '×'
|
||||||
|
255, // 'Ø'
|
||||||
|
255, // 'Ù'
|
||||||
|
255, // 'Ú'
|
||||||
|
255, // 'Û'
|
||||||
|
255, // 'Ü'
|
||||||
|
255, // 'Ý'
|
||||||
|
255, // 'Þ'
|
||||||
|
255, // 'ß'
|
||||||
|
255, // 'à'
|
||||||
|
255, // 'á'
|
||||||
|
255, // 'â'
|
||||||
|
255, // 'ã'
|
||||||
|
255, // 'ä'
|
||||||
|
255, // 'å'
|
||||||
|
255, // 'æ'
|
||||||
|
255, // 'ç'
|
||||||
|
255, // 'è'
|
||||||
|
255, // 'é'
|
||||||
|
255, // 'ê'
|
||||||
|
255, // 'ë'
|
||||||
|
255, // 'ì'
|
||||||
|
255, // 'í'
|
||||||
|
255, // 'î'
|
||||||
|
255, // 'ï'
|
||||||
|
255, // 'ð'
|
||||||
|
255, // 'ñ'
|
||||||
|
255, // 'ò'
|
||||||
|
255, // 'ó'
|
||||||
|
255, // 'ô'
|
||||||
|
255, // 'õ'
|
||||||
|
255, // 'ö'
|
||||||
|
255, // '÷'
|
||||||
|
255, // 'ø'
|
||||||
|
255, // 'ù'
|
||||||
|
255, // 'ú'
|
||||||
|
255, // 'û'
|
||||||
|
255, // 'ü'
|
||||||
|
255, // 'ý'
|
||||||
|
255, // 'þ'
|
||||||
|
255, // 'ÿ'
|
||||||
|
];
|
||||||
26
third_party/crates/aho-corasick-1.1.5/src/util/debug.rs
vendored
Normal file
26
third_party/crates/aho-corasick-1.1.5/src/util/debug.rs
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/// A type that wraps a single byte with a convenient fmt::Debug impl that
|
||||||
|
/// escapes the byte.
|
||||||
|
pub(crate) struct DebugByte(pub(crate) u8);
|
||||||
|
|
||||||
|
impl core::fmt::Debug for DebugByte {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
// Special case ASCII space. It's too hard to read otherwise, so
|
||||||
|
// put quotes around it. I sometimes wonder whether just '\x20' would
|
||||||
|
// be better...
|
||||||
|
if self.0 == b' ' {
|
||||||
|
return write!(f, "' '");
|
||||||
|
}
|
||||||
|
// 10 bytes is enough to cover any output from ascii::escape_default.
|
||||||
|
let mut bytes = [0u8; 10];
|
||||||
|
let mut len = 0;
|
||||||
|
for (i, mut b) in core::ascii::escape_default(self.0).enumerate() {
|
||||||
|
// capitalize \xab to \xAB
|
||||||
|
if i >= 2 && b'a' <= b && b <= b'f' {
|
||||||
|
b -= 32;
|
||||||
|
}
|
||||||
|
bytes[len] = b;
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
write!(f, "{}", core::str::from_utf8(&bytes[..len]).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
259
third_party/crates/aho-corasick-1.1.5/src/util/error.rs
vendored
Normal file
259
third_party/crates/aho-corasick-1.1.5/src/util/error.rs
vendored
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
use crate::util::{
|
||||||
|
primitives::{PatternID, SmallIndex},
|
||||||
|
search::MatchKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An error that occurred during the construction of an Aho-Corasick
|
||||||
|
/// automaton.
|
||||||
|
///
|
||||||
|
/// Build errors occur when some kind of limit has been exceeded, either in the
|
||||||
|
/// number of states, the number of patterns of the length of a pattern. These
|
||||||
|
/// limits aren't part of the public API, but they should generally be large
|
||||||
|
/// enough to handle most use cases.
|
||||||
|
///
|
||||||
|
/// When the `std` feature is enabled, this implements the `std::error::Error`
|
||||||
|
/// trait.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct BuildError {
|
||||||
|
kind: ErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of error that occurred.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum ErrorKind {
|
||||||
|
/// An error that occurs when allocating a new state would result in an
|
||||||
|
/// identifier that exceeds the capacity of a `StateID`.
|
||||||
|
StateIDOverflow {
|
||||||
|
/// The maximum possible id.
|
||||||
|
max: u64,
|
||||||
|
/// The maximum ID requested.
|
||||||
|
requested_max: u64,
|
||||||
|
},
|
||||||
|
/// An error that occurs when adding a pattern to an Aho-Corasick
|
||||||
|
/// automaton would result in an identifier that exceeds the capacity of a
|
||||||
|
/// `PatternID`.
|
||||||
|
PatternIDOverflow {
|
||||||
|
/// The maximum possible id.
|
||||||
|
max: u64,
|
||||||
|
/// The maximum ID requested.
|
||||||
|
requested_max: u64,
|
||||||
|
},
|
||||||
|
/// Occurs when a pattern string is given to the Aho-Corasick constructor
|
||||||
|
/// that is too long.
|
||||||
|
PatternTooLong {
|
||||||
|
/// The ID of the pattern that was too long.
|
||||||
|
pattern: PatternID,
|
||||||
|
/// The length that was too long.
|
||||||
|
len: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuildError {
|
||||||
|
pub(crate) fn state_id_overflow(
|
||||||
|
max: u64,
|
||||||
|
requested_max: u64,
|
||||||
|
) -> BuildError {
|
||||||
|
BuildError { kind: ErrorKind::StateIDOverflow { max, requested_max } }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pattern_id_overflow(
|
||||||
|
max: u64,
|
||||||
|
requested_max: u64,
|
||||||
|
) -> BuildError {
|
||||||
|
BuildError {
|
||||||
|
kind: ErrorKind::PatternIDOverflow { max, requested_max },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pattern_too_long(
|
||||||
|
pattern: PatternID,
|
||||||
|
len: usize,
|
||||||
|
) -> BuildError {
|
||||||
|
BuildError { kind: ErrorKind::PatternTooLong { pattern, len } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for BuildError {}
|
||||||
|
|
||||||
|
impl core::fmt::Display for BuildError {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
match self.kind {
|
||||||
|
ErrorKind::StateIDOverflow { max, requested_max } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"state identifier overflow: failed to create state ID \
|
||||||
|
from {}, which exceeds the max of {}",
|
||||||
|
requested_max, max,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ErrorKind::PatternIDOverflow { max, requested_max } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"pattern identifier overflow: failed to create pattern ID \
|
||||||
|
from {}, which exceeds the max of {}",
|
||||||
|
requested_max, max,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ErrorKind::PatternTooLong { pattern, len } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"pattern {} with length {} exceeds \
|
||||||
|
the maximum pattern length of {}",
|
||||||
|
pattern.as_usize(),
|
||||||
|
len,
|
||||||
|
SmallIndex::MAX.as_usize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error that occurred during an Aho-Corasick search.
|
||||||
|
///
|
||||||
|
/// An error that occurs during a search is limited to some kind of
|
||||||
|
/// misconfiguration that resulted in an illegal call. Stated differently,
|
||||||
|
/// whether an error occurs is not dependent on the specific bytes in the
|
||||||
|
/// haystack.
|
||||||
|
///
|
||||||
|
/// Examples of misconfiguration:
|
||||||
|
///
|
||||||
|
/// * Executing a stream or overlapping search on a searcher that was built was
|
||||||
|
/// something other than [`MatchKind::Standard`](crate::MatchKind::Standard)
|
||||||
|
/// semantics.
|
||||||
|
/// * Requested an anchored or an unanchored search on a searcher that doesn't
|
||||||
|
/// support unanchored or anchored searches, respectively.
|
||||||
|
///
|
||||||
|
/// When the `std` feature is enabled, this implements the `std::error::Error`
|
||||||
|
/// trait.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MatchError(alloc::boxed::Box<MatchErrorKind>);
|
||||||
|
|
||||||
|
impl MatchError {
|
||||||
|
/// Create a new error value with the given kind.
|
||||||
|
///
|
||||||
|
/// This is a more verbose version of the kind-specific constructors, e.g.,
|
||||||
|
/// `MatchError::unsupported_stream`.
|
||||||
|
pub fn new(kind: MatchErrorKind) -> MatchError {
|
||||||
|
MatchError(alloc::boxed::Box::new(kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the underlying error kind.
|
||||||
|
pub fn kind(&self) -> &MatchErrorKind {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new "invalid anchored search" error. This occurs when the
|
||||||
|
/// caller requests an anchored search but where anchored searches aren't
|
||||||
|
/// supported.
|
||||||
|
///
|
||||||
|
/// This is the same as calling `MatchError::new` with a
|
||||||
|
/// [`MatchErrorKind::InvalidInputAnchored`] kind.
|
||||||
|
pub fn invalid_input_anchored() -> MatchError {
|
||||||
|
MatchError::new(MatchErrorKind::InvalidInputAnchored)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new "invalid unanchored search" error. This occurs when the
|
||||||
|
/// caller requests an unanchored search but where unanchored searches
|
||||||
|
/// aren't supported.
|
||||||
|
///
|
||||||
|
/// This is the same as calling `MatchError::new` with a
|
||||||
|
/// [`MatchErrorKind::InvalidInputUnanchored`] kind.
|
||||||
|
pub fn invalid_input_unanchored() -> MatchError {
|
||||||
|
MatchError::new(MatchErrorKind::InvalidInputUnanchored)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new "unsupported stream search" error. This occurs when the
|
||||||
|
/// caller requests a stream search while using an Aho-Corasick automaton
|
||||||
|
/// with a match kind other than [`MatchKind::Standard`].
|
||||||
|
///
|
||||||
|
/// The match kind given should be the match kind of the automaton. It
|
||||||
|
/// should never be `MatchKind::Standard`.
|
||||||
|
pub fn unsupported_stream(got: MatchKind) -> MatchError {
|
||||||
|
MatchError::new(MatchErrorKind::UnsupportedStream { got })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new "unsupported overlapping search" error. This occurs when
|
||||||
|
/// the caller requests an overlapping search while using an Aho-Corasick
|
||||||
|
/// automaton with a match kind other than [`MatchKind::Standard`].
|
||||||
|
///
|
||||||
|
/// The match kind given should be the match kind of the automaton. It
|
||||||
|
/// should never be `MatchKind::Standard`.
|
||||||
|
pub fn unsupported_overlapping(got: MatchKind) -> MatchError {
|
||||||
|
MatchError::new(MatchErrorKind::UnsupportedOverlapping { got })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new "unsupported empty pattern" error. This occurs when the
|
||||||
|
/// caller requests a search for which matching an automaton that contains
|
||||||
|
/// an empty pattern string is not supported.
|
||||||
|
pub fn unsupported_empty() -> MatchError {
|
||||||
|
MatchError::new(MatchErrorKind::UnsupportedEmpty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The underlying kind of a [`MatchError`].
|
||||||
|
///
|
||||||
|
/// This is a **non-exhaustive** enum. That means new variants may be added in
|
||||||
|
/// a semver-compatible release.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum MatchErrorKind {
|
||||||
|
/// An error indicating that an anchored search was requested, but from a
|
||||||
|
/// searcher that was built without anchored support.
|
||||||
|
InvalidInputAnchored,
|
||||||
|
/// An error indicating that an unanchored search was requested, but from a
|
||||||
|
/// searcher that was built without unanchored support.
|
||||||
|
InvalidInputUnanchored,
|
||||||
|
/// An error indicating that a stream search was attempted on an
|
||||||
|
/// Aho-Corasick automaton with an unsupported `MatchKind`.
|
||||||
|
UnsupportedStream {
|
||||||
|
/// The match semantics for the automaton that was used.
|
||||||
|
got: MatchKind,
|
||||||
|
},
|
||||||
|
/// An error indicating that an overlapping search was attempted on an
|
||||||
|
/// Aho-Corasick automaton with an unsupported `MatchKind`.
|
||||||
|
UnsupportedOverlapping {
|
||||||
|
/// The match semantics for the automaton that was used.
|
||||||
|
got: MatchKind,
|
||||||
|
},
|
||||||
|
/// An error indicating that the operation requested doesn't support
|
||||||
|
/// automatons that contain an empty pattern string.
|
||||||
|
UnsupportedEmpty,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for MatchError {}
|
||||||
|
|
||||||
|
impl core::fmt::Display for MatchError {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
match *self.kind() {
|
||||||
|
MatchErrorKind::InvalidInputAnchored => {
|
||||||
|
write!(f, "anchored searches are not supported or enabled")
|
||||||
|
}
|
||||||
|
MatchErrorKind::InvalidInputUnanchored => {
|
||||||
|
write!(f, "unanchored searches are not supported or enabled")
|
||||||
|
}
|
||||||
|
MatchErrorKind::UnsupportedStream { got } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"match kind {:?} does not support stream searching",
|
||||||
|
got,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MatchErrorKind::UnsupportedOverlapping { got } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"match kind {:?} does not support overlapping searches",
|
||||||
|
got,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MatchErrorKind::UnsupportedEmpty => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"matching with an empty pattern string is not \
|
||||||
|
supported for this operation",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
278
third_party/crates/aho-corasick-1.1.5/src/util/int.rs
vendored
Normal file
278
third_party/crates/aho-corasick-1.1.5/src/util/int.rs
vendored
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
/*!
|
||||||
|
This module provides several integer oriented traits for converting between
|
||||||
|
both fixed size integers and integers whose size varies based on the target
|
||||||
|
(like `usize`).
|
||||||
|
|
||||||
|
The main design principle for this module is to centralize all uses of `as`.
|
||||||
|
The thinking here is that `as` makes it very easy to perform accidental lossy
|
||||||
|
conversions, and if we centralize all its uses here under more descriptive
|
||||||
|
higher level operations, its use and correctness becomes easier to audit.
|
||||||
|
|
||||||
|
This was copied mostly wholesale from `regex-automata`.
|
||||||
|
|
||||||
|
NOTE: for simplicity, we don't take target pointer width into account here for
|
||||||
|
`usize` conversions. Since we currently only panic in debug mode, skipping the
|
||||||
|
check when it can be proven it isn't needed at compile time doesn't really
|
||||||
|
matter. Now, if we wind up wanting to do as many checks as possible in release
|
||||||
|
mode, then we would want to skip those when we know the conversions are always
|
||||||
|
non-lossy.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// We define a little more than what we need, but I'd rather just have
|
||||||
|
// everything via a consistent and uniform API then have holes.
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
pub(crate) trait U8 {
|
||||||
|
fn as_usize(self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U8 for u8 {
|
||||||
|
fn as_usize(self) -> usize {
|
||||||
|
usize::from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait U16 {
|
||||||
|
fn as_usize(self) -> usize;
|
||||||
|
fn low_u8(self) -> u8;
|
||||||
|
fn high_u8(self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U16 for u16 {
|
||||||
|
fn as_usize(self) -> usize {
|
||||||
|
usize::from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn low_u8(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn high_u8(self) -> u8 {
|
||||||
|
(self >> 8) as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait U32 {
|
||||||
|
fn as_usize(self) -> usize;
|
||||||
|
fn low_u8(self) -> u8;
|
||||||
|
fn low_u16(self) -> u16;
|
||||||
|
fn high_u16(self) -> u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U32 for u32 {
|
||||||
|
#[inline]
|
||||||
|
fn as_usize(self) -> usize {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
usize::try_from(self).expect("u32 overflowed usize")
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
self as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn low_u8(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn low_u16(self) -> u16 {
|
||||||
|
self as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn high_u16(self) -> u16 {
|
||||||
|
(self >> 16) as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait U64 {
|
||||||
|
fn as_usize(self) -> usize;
|
||||||
|
fn low_u8(self) -> u8;
|
||||||
|
fn low_u16(self) -> u16;
|
||||||
|
fn low_u32(self) -> u32;
|
||||||
|
fn high_u32(self) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl U64 for u64 {
|
||||||
|
fn as_usize(self) -> usize {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
usize::try_from(self).expect("u64 overflowed usize")
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
self as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn low_u8(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn low_u16(self) -> u16 {
|
||||||
|
self as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn low_u32(self) -> u32 {
|
||||||
|
self as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn high_u32(self) -> u32 {
|
||||||
|
(self >> 32) as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait I8 {
|
||||||
|
fn as_usize(self) -> usize;
|
||||||
|
fn to_bits(self) -> u8;
|
||||||
|
fn from_bits(n: u8) -> i8;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl I8 for i8 {
|
||||||
|
fn as_usize(self) -> usize {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
usize::try_from(self).expect("i8 overflowed usize")
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
self as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_bits(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_bits(n: u8) -> i8 {
|
||||||
|
n as i8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait I32 {
|
||||||
|
fn as_usize(self) -> usize;
|
||||||
|
fn to_bits(self) -> u32;
|
||||||
|
fn from_bits(n: u32) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl I32 for i32 {
|
||||||
|
fn as_usize(self) -> usize {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
usize::try_from(self).expect("i32 overflowed usize")
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
self as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_bits(self) -> u32 {
|
||||||
|
self as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_bits(n: u32) -> i32 {
|
||||||
|
n as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait I64 {
|
||||||
|
fn as_usize(self) -> usize;
|
||||||
|
fn to_bits(self) -> u64;
|
||||||
|
fn from_bits(n: u64) -> i64;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl I64 for i64 {
|
||||||
|
fn as_usize(self) -> usize {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
usize::try_from(self).expect("i64 overflowed usize")
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
self as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_bits(self) -> u64 {
|
||||||
|
self as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_bits(n: u64) -> i64 {
|
||||||
|
n as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Usize {
|
||||||
|
fn as_u8(self) -> u8;
|
||||||
|
fn as_u16(self) -> u16;
|
||||||
|
fn as_u32(self) -> u32;
|
||||||
|
fn as_u64(self) -> u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Usize for usize {
|
||||||
|
fn as_u8(self) -> u8 {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
u8::try_from(self).expect("usize overflowed u8")
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_u16(self) -> u16 {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
u16::try_from(self).expect("usize overflowed u16")
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
self as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_u32(self) -> u32 {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
u32::try_from(self).expect("usize overflowed u32")
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
self as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_u64(self) -> u64 {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
u64::try_from(self).expect("usize overflowed u64")
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
self as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointers aren't integers, but we convert pointers to integers to perform
|
||||||
|
// offset arithmetic in some places. (And no, we don't convert the integers
|
||||||
|
// back to pointers.) So add 'as_usize' conversions here too for completeness.
|
||||||
|
//
|
||||||
|
// These 'as' casts are actually okay because they're always non-lossy. But the
|
||||||
|
// idea here is to just try and remove as much 'as' as possible, particularly
|
||||||
|
// in this crate where we are being really paranoid about offsets and making
|
||||||
|
// sure we don't panic on inputs that might be untrusted. This way, the 'as'
|
||||||
|
// casts become easier to audit if they're all in one place, even when some of
|
||||||
|
// them are actually okay 100% of the time.
|
||||||
|
|
||||||
|
pub(crate) trait Pointer {
|
||||||
|
fn as_usize(self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Pointer for *const T {
|
||||||
|
fn as_usize(self) -> usize {
|
||||||
|
self as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
12
third_party/crates/aho-corasick-1.1.5/src/util/mod.rs
vendored
Normal file
12
third_party/crates/aho-corasick-1.1.5/src/util/mod.rs
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
pub(crate) mod alphabet;
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub(crate) mod buffer;
|
||||||
|
pub(crate) mod byte_frequencies;
|
||||||
|
pub(crate) mod debug;
|
||||||
|
pub(crate) mod error;
|
||||||
|
pub(crate) mod int;
|
||||||
|
pub(crate) mod prefilter;
|
||||||
|
pub(crate) mod primitives;
|
||||||
|
pub(crate) mod remapper;
|
||||||
|
pub(crate) mod search;
|
||||||
|
pub(crate) mod special;
|
||||||
924
third_party/crates/aho-corasick-1.1.5/src/util/prefilter.rs
vendored
Normal file
924
third_party/crates/aho-corasick-1.1.5/src/util/prefilter.rs
vendored
Normal file
@@ -0,0 +1,924 @@
|
|||||||
|
use core::{
|
||||||
|
cmp,
|
||||||
|
fmt::Debug,
|
||||||
|
panic::{RefUnwindSafe, UnwindSafe},
|
||||||
|
u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
use alloc::{sync::Arc, vec, vec::Vec};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
packed,
|
||||||
|
util::{
|
||||||
|
alphabet::ByteSet,
|
||||||
|
search::{Match, MatchKind, Span},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A prefilter for accelerating a search.
|
||||||
|
///
|
||||||
|
/// This crate uses prefilters in the core search implementations to accelerate
|
||||||
|
/// common cases. They typically only apply to cases where there are a small
|
||||||
|
/// number of patterns (less than 100 or so), but when they do, thoughput can
|
||||||
|
/// be boosted considerably, perhaps by an order of magnitude. When a prefilter
|
||||||
|
/// is active, it is used whenever a search enters an automaton's start state.
|
||||||
|
///
|
||||||
|
/// Currently, prefilters cannot be constructed by
|
||||||
|
/// callers. A `Prefilter` can only be accessed via the
|
||||||
|
/// [`Automaton::prefilter`](crate::automaton::Automaton::prefilter)
|
||||||
|
/// method and used to execute a search. In other words, a prefilter can be
|
||||||
|
/// used to optimize your own search implementation if necessary, but cannot do
|
||||||
|
/// much else. If you have a use case for more APIs, please submit an issue.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Prefilter {
|
||||||
|
finder: Arc<dyn PrefilterI>,
|
||||||
|
memory_usage: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Prefilter {
|
||||||
|
/// Execute a search in the haystack within the span given. If a match or
|
||||||
|
/// a possible match is returned, then it is guaranteed to occur within
|
||||||
|
/// the bounds of the span.
|
||||||
|
///
|
||||||
|
/// If the span provided is invalid for the given haystack, then behavior
|
||||||
|
/// is unspecified.
|
||||||
|
#[inline]
|
||||||
|
pub fn find_in(&self, haystack: &[u8], span: Span) -> Candidate {
|
||||||
|
self.finder.find_in(haystack, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn memory_usage(&self) -> usize {
|
||||||
|
self.memory_usage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A candidate is the result of running a prefilter on a haystack at a
|
||||||
|
/// particular position.
|
||||||
|
///
|
||||||
|
/// The result is either no match, a confirmed match or a possible match.
|
||||||
|
///
|
||||||
|
/// When no match is returned, the prefilter is guaranteeing that no possible
|
||||||
|
/// match can be found in the haystack, and the caller may trust this. That is,
|
||||||
|
/// all correct prefilters must never report false negatives.
|
||||||
|
///
|
||||||
|
/// In some cases, a prefilter can confirm a match very quickly, in which case,
|
||||||
|
/// the caller may use this to stop what it's doing and report the match. In
|
||||||
|
/// this case, prefilter implementations must never report a false positive.
|
||||||
|
/// In other cases, the prefilter can only report a potential match, in which
|
||||||
|
/// case the callers must attempt to confirm the match. In this case, prefilter
|
||||||
|
/// implementations are permitted to return false positives.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Candidate {
|
||||||
|
/// No match was found. Since false negatives are not possible, this means
|
||||||
|
/// the search can quit as it is guaranteed not to find another match.
|
||||||
|
None,
|
||||||
|
/// A confirmed match was found. Callers do not need to confirm it.
|
||||||
|
Match(Match),
|
||||||
|
/// The start of a possible match was found. Callers must confirm it before
|
||||||
|
/// reporting it as a match.
|
||||||
|
PossibleStartOfMatch(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Candidate {
|
||||||
|
/// Convert this candidate into an option. This is useful when callers
|
||||||
|
/// do not distinguish between true positives and false positives (i.e.,
|
||||||
|
/// the caller must always confirm the match).
|
||||||
|
pub fn into_option(self) -> Option<usize> {
|
||||||
|
match self {
|
||||||
|
Candidate::None => None,
|
||||||
|
Candidate::Match(ref m) => Some(m.start()),
|
||||||
|
Candidate::PossibleStartOfMatch(start) => Some(start),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A prefilter describes the behavior of fast literal scanners for quickly
|
||||||
|
/// skipping past bytes in the haystack that we know cannot possibly
|
||||||
|
/// participate in a match.
|
||||||
|
trait PrefilterI:
|
||||||
|
Send + Sync + RefUnwindSafe + UnwindSafe + Debug + 'static
|
||||||
|
{
|
||||||
|
/// Returns the next possible match candidate. This may yield false
|
||||||
|
/// positives, so callers must confirm a match starting at the position
|
||||||
|
/// returned. This, however, must never produce false negatives. That is,
|
||||||
|
/// this must, at minimum, return the starting position of the next match
|
||||||
|
/// in the given haystack after or at the given position.
|
||||||
|
fn find_in(&self, haystack: &[u8], span: Span) -> Candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: PrefilterI + ?Sized> PrefilterI for Arc<P> {
|
||||||
|
#[inline(always)]
|
||||||
|
fn find_in(&self, haystack: &[u8], span: Span) -> Candidate {
|
||||||
|
(**self).find_in(haystack, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A builder for constructing the best possible prefilter. When constructed,
|
||||||
|
/// this builder will heuristically select the best prefilter it can build,
|
||||||
|
/// if any, and discard the rest.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Builder {
|
||||||
|
count: usize,
|
||||||
|
ascii_case_insensitive: bool,
|
||||||
|
start_bytes: StartBytesBuilder,
|
||||||
|
rare_bytes: RareBytesBuilder,
|
||||||
|
memmem: MemmemBuilder,
|
||||||
|
packed: Option<packed::Builder>,
|
||||||
|
// If we run across a condition that suggests we shouldn't use a prefilter
|
||||||
|
// at all (like an empty pattern), then disable prefilters entirely.
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
/// Create a new builder for constructing the best possible prefilter.
|
||||||
|
pub(crate) fn new(kind: MatchKind) -> Builder {
|
||||||
|
let pbuilder = kind
|
||||||
|
.as_packed()
|
||||||
|
.map(|kind| packed::Config::new().match_kind(kind).builder());
|
||||||
|
Builder {
|
||||||
|
count: 0,
|
||||||
|
ascii_case_insensitive: false,
|
||||||
|
start_bytes: StartBytesBuilder::new(),
|
||||||
|
rare_bytes: RareBytesBuilder::new(),
|
||||||
|
memmem: MemmemBuilder::default(),
|
||||||
|
packed: pbuilder,
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable ASCII case insensitivity. When set, byte strings added to this
|
||||||
|
/// builder will be interpreted without respect to ASCII case.
|
||||||
|
pub(crate) fn ascii_case_insensitive(mut self, yes: bool) -> Builder {
|
||||||
|
self.ascii_case_insensitive = yes;
|
||||||
|
self.start_bytes = self.start_bytes.ascii_case_insensitive(yes);
|
||||||
|
self.rare_bytes = self.rare_bytes.ascii_case_insensitive(yes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a prefilter suitable for quickly finding potential matches.
|
||||||
|
///
|
||||||
|
/// All patterns added to an Aho-Corasick automaton should be added to this
|
||||||
|
/// builder before attempting to construct the prefilter.
|
||||||
|
pub(crate) fn build(&self) -> Option<Prefilter> {
|
||||||
|
if !self.enabled {
|
||||||
|
debug!("prefilter not enabled, skipping");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// If we only have one pattern, then deferring to memmem is always
|
||||||
|
// the best choice. This is kind of a weird case, because, well, why
|
||||||
|
// use Aho-Corasick if you only have one pattern? But maybe you don't
|
||||||
|
// know exactly how many patterns you'll get up front, and you need to
|
||||||
|
// support the option of multiple patterns. So instead of relying on
|
||||||
|
// the caller to branch and use memmem explicitly, we just do it for
|
||||||
|
// them.
|
||||||
|
if !self.ascii_case_insensitive {
|
||||||
|
if let Some(pre) = self.memmem.build() {
|
||||||
|
debug!("using memmem prefilter");
|
||||||
|
return Some(pre);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (packed, patlen, minlen) = if self.ascii_case_insensitive {
|
||||||
|
(None, usize::MAX, 0)
|
||||||
|
} else {
|
||||||
|
let patlen = self.packed.as_ref().map_or(usize::MAX, |p| p.len());
|
||||||
|
let minlen = self.packed.as_ref().map_or(0, |p| p.minimum_len());
|
||||||
|
let packed =
|
||||||
|
self.packed.as_ref().and_then(|b| b.build()).map(|s| {
|
||||||
|
let memory_usage = s.memory_usage();
|
||||||
|
debug!(
|
||||||
|
"built packed prefilter (len: {}, \
|
||||||
|
minimum pattern len: {}, memory usage: {}) \
|
||||||
|
for consideration",
|
||||||
|
patlen, minlen, memory_usage,
|
||||||
|
);
|
||||||
|
Prefilter { finder: Arc::new(Packed(s)), memory_usage }
|
||||||
|
});
|
||||||
|
(packed, patlen, minlen)
|
||||||
|
};
|
||||||
|
match (self.start_bytes.build(), self.rare_bytes.build()) {
|
||||||
|
// If we could build both start and rare prefilters, then there are
|
||||||
|
// a few cases in which we'd want to use the start-byte prefilter
|
||||||
|
// over the rare-byte prefilter, since the former has lower
|
||||||
|
// overhead.
|
||||||
|
(prestart @ Some(_), prerare @ Some(_)) => {
|
||||||
|
debug!(
|
||||||
|
"both start (len={}, rank={}) and \
|
||||||
|
rare (len={}, rank={}) byte prefilters \
|
||||||
|
are available",
|
||||||
|
self.start_bytes.count,
|
||||||
|
self.start_bytes.rank_sum,
|
||||||
|
self.rare_bytes.count,
|
||||||
|
self.rare_bytes.rank_sum,
|
||||||
|
);
|
||||||
|
if patlen <= 16
|
||||||
|
&& minlen >= 2
|
||||||
|
&& self.start_bytes.count >= 3
|
||||||
|
&& self.rare_bytes.count >= 3
|
||||||
|
{
|
||||||
|
debug!(
|
||||||
|
"start and rare byte prefilters available, but \
|
||||||
|
they're probably slower than packed so using \
|
||||||
|
packed"
|
||||||
|
);
|
||||||
|
return packed;
|
||||||
|
}
|
||||||
|
// If the start-byte prefilter can scan for a smaller number
|
||||||
|
// of bytes than the rare-byte prefilter, then it's probably
|
||||||
|
// faster.
|
||||||
|
let has_fewer_bytes =
|
||||||
|
self.start_bytes.count < self.rare_bytes.count;
|
||||||
|
// Otherwise, if the combined frequency rank of the detected
|
||||||
|
// bytes in the start-byte prefilter is "close" to the combined
|
||||||
|
// frequency rank of the rare-byte prefilter, then we pick
|
||||||
|
// the start-byte prefilter even if the rare-byte prefilter
|
||||||
|
// heuristically searches for rare bytes. This is because the
|
||||||
|
// rare-byte prefilter has higher constant costs, so we tend to
|
||||||
|
// prefer the start-byte prefilter when we can.
|
||||||
|
let has_rarer_bytes =
|
||||||
|
self.start_bytes.rank_sum <= self.rare_bytes.rank_sum + 50;
|
||||||
|
if has_fewer_bytes {
|
||||||
|
debug!(
|
||||||
|
"using start byte prefilter because it has fewer
|
||||||
|
bytes to search for than the rare byte prefilter",
|
||||||
|
);
|
||||||
|
prestart
|
||||||
|
} else if has_rarer_bytes {
|
||||||
|
debug!(
|
||||||
|
"using start byte prefilter because its byte \
|
||||||
|
frequency rank was determined to be \
|
||||||
|
\"good enough\" relative to the rare byte prefilter \
|
||||||
|
byte frequency rank",
|
||||||
|
);
|
||||||
|
prestart
|
||||||
|
} else {
|
||||||
|
debug!("using rare byte prefilter");
|
||||||
|
prerare
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(prestart @ Some(_), None) => {
|
||||||
|
if patlen <= 16 && minlen >= 2 && self.start_bytes.count >= 3 {
|
||||||
|
debug!(
|
||||||
|
"start byte prefilter available, but \
|
||||||
|
it's probably slower than packed so using \
|
||||||
|
packed"
|
||||||
|
);
|
||||||
|
return packed;
|
||||||
|
}
|
||||||
|
debug!(
|
||||||
|
"have start byte prefilter but not rare byte prefilter, \
|
||||||
|
so using start byte prefilter",
|
||||||
|
);
|
||||||
|
prestart
|
||||||
|
}
|
||||||
|
(None, prerare @ Some(_)) => {
|
||||||
|
if patlen <= 16 && minlen >= 2 && self.rare_bytes.count >= 3 {
|
||||||
|
debug!(
|
||||||
|
"rare byte prefilter available, but \
|
||||||
|
it's probably slower than packed so using \
|
||||||
|
packed"
|
||||||
|
);
|
||||||
|
return packed;
|
||||||
|
}
|
||||||
|
debug!(
|
||||||
|
"have rare byte prefilter but not start byte prefilter, \
|
||||||
|
so using rare byte prefilter",
|
||||||
|
);
|
||||||
|
prerare
|
||||||
|
}
|
||||||
|
(None, None) if self.ascii_case_insensitive => {
|
||||||
|
debug!(
|
||||||
|
"no start or rare byte prefilter and ASCII case \
|
||||||
|
insensitivity was enabled, so skipping prefilter",
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
(None, None) => {
|
||||||
|
if packed.is_some() {
|
||||||
|
debug!("falling back to packed prefilter");
|
||||||
|
} else {
|
||||||
|
debug!("no prefilter available");
|
||||||
|
}
|
||||||
|
packed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a literal string to this prefilter builder.
|
||||||
|
pub(crate) fn add(&mut self, bytes: &[u8]) {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
self.enabled = false;
|
||||||
|
}
|
||||||
|
if !self.enabled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.count += 1;
|
||||||
|
self.start_bytes.add(bytes);
|
||||||
|
self.rare_bytes.add(bytes);
|
||||||
|
self.memmem.add(bytes);
|
||||||
|
if let Some(ref mut pbuilder) = self.packed {
|
||||||
|
pbuilder.add(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type that wraps a packed searcher and implements the `Prefilter`
|
||||||
|
/// interface.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Packed(packed::Searcher);
|
||||||
|
|
||||||
|
impl PrefilterI for Packed {
|
||||||
|
fn find_in(&self, haystack: &[u8], span: Span) -> Candidate {
|
||||||
|
self.0
|
||||||
|
.find_in(haystack, span)
|
||||||
|
.map_or(Candidate::None, Candidate::Match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A builder for constructing a prefilter that uses memmem.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct MemmemBuilder {
|
||||||
|
/// The number of patterns that have been added.
|
||||||
|
count: usize,
|
||||||
|
/// The singular pattern to search for. This is only set when count==1.
|
||||||
|
one: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemmemBuilder {
|
||||||
|
fn build(&self) -> Option<Prefilter> {
|
||||||
|
#[cfg(all(feature = "std", feature = "perf-literal"))]
|
||||||
|
fn imp(builder: &MemmemBuilder) -> Option<Prefilter> {
|
||||||
|
let pattern = builder.one.as_ref()?;
|
||||||
|
assert_eq!(1, builder.count);
|
||||||
|
let finder = Arc::new(Memmem(
|
||||||
|
memchr::memmem::Finder::new(pattern).into_owned(),
|
||||||
|
));
|
||||||
|
let memory_usage = pattern.len();
|
||||||
|
Some(Prefilter { finder, memory_usage })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(all(feature = "std", feature = "perf-literal")))]
|
||||||
|
fn imp(_: &MemmemBuilder) -> Option<Prefilter> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
imp(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, bytes: &[u8]) {
|
||||||
|
self.count += 1;
|
||||||
|
if self.count == 1 {
|
||||||
|
self.one = Some(bytes.to_vec());
|
||||||
|
} else {
|
||||||
|
self.one = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type that wraps a SIMD accelerated single substring search from the
|
||||||
|
/// `memchr` crate for use as a prefilter.
|
||||||
|
///
|
||||||
|
/// Currently, this prefilter is only active for Aho-Corasick searchers with
|
||||||
|
/// a single pattern. In theory, this could be extended to support searchers
|
||||||
|
/// that have a common prefix of more than one byte (for one byte, we would use
|
||||||
|
/// memchr), but it's not clear if it's worth it or not.
|
||||||
|
///
|
||||||
|
/// Also, unfortunately, this currently also requires the 'std' feature to
|
||||||
|
/// be enabled. That's because memchr doesn't have a no-std-but-with-alloc
|
||||||
|
/// mode, and so APIs like Finder::into_owned aren't available when 'std' is
|
||||||
|
/// disabled. But there should be an 'alloc' feature that brings in APIs like
|
||||||
|
/// Finder::into_owned but doesn't use std-only features like runtime CPU
|
||||||
|
/// feature detection.
|
||||||
|
#[cfg(all(feature = "std", feature = "perf-literal"))]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Memmem(memchr::memmem::Finder<'static>);
|
||||||
|
|
||||||
|
#[cfg(all(feature = "std", feature = "perf-literal"))]
|
||||||
|
impl PrefilterI for Memmem {
|
||||||
|
fn find_in(&self, haystack: &[u8], span: Span) -> Candidate {
|
||||||
|
use crate::util::primitives::PatternID;
|
||||||
|
|
||||||
|
self.0.find(&haystack[span]).map_or(Candidate::None, |i| {
|
||||||
|
let start = span.start + i;
|
||||||
|
let end = start + self.0.needle().len();
|
||||||
|
// N.B. We can declare a match and use a fixed pattern ID here
|
||||||
|
// because a Memmem prefilter is only ever created for searchers
|
||||||
|
// with exactly one pattern. Thus, every match is always a match
|
||||||
|
// and it is always for the first and only pattern.
|
||||||
|
Candidate::Match(Match::new(PatternID::ZERO, start..end))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A builder for constructing a rare byte prefilter.
|
||||||
|
///
|
||||||
|
/// A rare byte prefilter attempts to pick out a small set of rare bytes that
|
||||||
|
/// occurr in the patterns, and then quickly scan to matches of those rare
|
||||||
|
/// bytes.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct RareBytesBuilder {
|
||||||
|
/// Whether this prefilter should account for ASCII case insensitivity or
|
||||||
|
/// not.
|
||||||
|
ascii_case_insensitive: bool,
|
||||||
|
/// A set of rare bytes, indexed by byte value.
|
||||||
|
rare_set: ByteSet,
|
||||||
|
/// A set of byte offsets associated with bytes in a pattern. An entry
|
||||||
|
/// corresponds to a particular bytes (its index) and is only non-zero if
|
||||||
|
/// the byte occurred at an offset greater than 0 in at least one pattern.
|
||||||
|
///
|
||||||
|
/// If a byte's offset is not representable in 8 bits, then the rare bytes
|
||||||
|
/// prefilter becomes inert.
|
||||||
|
byte_offsets: RareByteOffsets,
|
||||||
|
/// Whether this is available as a prefilter or not. This can be set to
|
||||||
|
/// false during construction if a condition is seen that invalidates the
|
||||||
|
/// use of the rare-byte prefilter.
|
||||||
|
available: bool,
|
||||||
|
/// The number of bytes set to an active value in `byte_offsets`.
|
||||||
|
count: usize,
|
||||||
|
/// The sum of frequency ranks for the rare bytes detected. This is
|
||||||
|
/// intended to give a heuristic notion of how rare the bytes are.
|
||||||
|
rank_sum: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of byte offsets, keyed by byte.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct RareByteOffsets {
|
||||||
|
/// Each entry corresponds to the maximum offset of the corresponding
|
||||||
|
/// byte across all patterns seen.
|
||||||
|
set: [RareByteOffset; 256],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RareByteOffsets {
|
||||||
|
/// Create a new empty set of rare byte offsets.
|
||||||
|
pub(crate) fn empty() -> RareByteOffsets {
|
||||||
|
RareByteOffsets { set: [RareByteOffset::default(); 256] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add the given offset for the given byte to this set. If the offset is
|
||||||
|
/// greater than the existing offset, then it overwrites the previous
|
||||||
|
/// value and returns false. If there is no previous value set, then this
|
||||||
|
/// sets it and returns true.
|
||||||
|
pub(crate) fn set(&mut self, byte: u8, off: RareByteOffset) {
|
||||||
|
self.set[byte as usize].max =
|
||||||
|
cmp::max(self.set[byte as usize].max, off.max);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for RareByteOffsets {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
let mut offsets = vec![];
|
||||||
|
for off in self.set.iter() {
|
||||||
|
if off.max > 0 {
|
||||||
|
offsets.push(off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.debug_struct("RareByteOffsets").field("set", &offsets).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Offsets associated with an occurrence of a "rare" byte in any of the
|
||||||
|
/// patterns used to construct a single Aho-Corasick automaton.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct RareByteOffset {
|
||||||
|
/// The maximum offset at which a particular byte occurs from the start
|
||||||
|
/// of any pattern. This is used as a shift amount. That is, when an
|
||||||
|
/// occurrence of this byte is found, the candidate position reported by
|
||||||
|
/// the prefilter is `position_of_byte - max`, such that the automaton
|
||||||
|
/// will begin its search at a position that is guaranteed to observe a
|
||||||
|
/// match.
|
||||||
|
///
|
||||||
|
/// To avoid accidentally quadratic behavior, a prefilter is considered
|
||||||
|
/// ineffective when it is asked to start scanning from a position that it
|
||||||
|
/// has already scanned past.
|
||||||
|
///
|
||||||
|
/// Using a `u8` here means that if we ever see a pattern that's longer
|
||||||
|
/// than 255 bytes, then the entire rare byte prefilter is disabled.
|
||||||
|
max: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RareByteOffset {
|
||||||
|
fn default() -> RareByteOffset {
|
||||||
|
RareByteOffset { max: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RareByteOffset {
|
||||||
|
/// Create a new rare byte offset. If the given offset is too big, then
|
||||||
|
/// None is returned. In that case, callers should render the rare bytes
|
||||||
|
/// prefilter inert.
|
||||||
|
fn new(max: usize) -> Option<RareByteOffset> {
|
||||||
|
if max > u8::MAX as usize {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(RareByteOffset { max: max as u8 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RareBytesBuilder {
|
||||||
|
/// Create a new builder for constructing a rare byte prefilter.
|
||||||
|
fn new() -> RareBytesBuilder {
|
||||||
|
RareBytesBuilder {
|
||||||
|
ascii_case_insensitive: false,
|
||||||
|
rare_set: ByteSet::empty(),
|
||||||
|
byte_offsets: RareByteOffsets::empty(),
|
||||||
|
available: true,
|
||||||
|
count: 0,
|
||||||
|
rank_sum: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable ASCII case insensitivity. When set, byte strings added to this
|
||||||
|
/// builder will be interpreted without respect to ASCII case.
|
||||||
|
fn ascii_case_insensitive(mut self, yes: bool) -> RareBytesBuilder {
|
||||||
|
self.ascii_case_insensitive = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the rare bytes prefilter.
|
||||||
|
///
|
||||||
|
/// If there are more than 3 distinct rare bytes found, or if heuristics
|
||||||
|
/// otherwise determine that this prefilter should not be used, then `None`
|
||||||
|
/// is returned.
|
||||||
|
fn build(&self) -> Option<Prefilter> {
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
fn imp(builder: &RareBytesBuilder) -> Option<Prefilter> {
|
||||||
|
if !builder.available || builder.count > 3 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let (mut bytes, mut len) = ([0; 3], 0);
|
||||||
|
for b in 0..=255 {
|
||||||
|
if builder.rare_set.contains(b) {
|
||||||
|
bytes[len] = b;
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let finder: Arc<dyn PrefilterI> = match len {
|
||||||
|
0 => return None,
|
||||||
|
1 => Arc::new(RareBytesOne {
|
||||||
|
byte1: bytes[0],
|
||||||
|
offset: builder.byte_offsets.set[bytes[0] as usize],
|
||||||
|
}),
|
||||||
|
2 => Arc::new(RareBytesTwo {
|
||||||
|
offsets: builder.byte_offsets,
|
||||||
|
byte1: bytes[0],
|
||||||
|
byte2: bytes[1],
|
||||||
|
}),
|
||||||
|
3 => Arc::new(RareBytesThree {
|
||||||
|
offsets: builder.byte_offsets,
|
||||||
|
byte1: bytes[0],
|
||||||
|
byte2: bytes[1],
|
||||||
|
byte3: bytes[2],
|
||||||
|
}),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Some(Prefilter { finder, memory_usage: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "perf-literal"))]
|
||||||
|
fn imp(_: &RareBytesBuilder) -> Option<Prefilter> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
imp(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a byte string to this builder.
|
||||||
|
///
|
||||||
|
/// All patterns added to an Aho-Corasick automaton should be added to this
|
||||||
|
/// builder before attempting to construct the prefilter.
|
||||||
|
fn add(&mut self, bytes: &[u8]) {
|
||||||
|
// If we've already given up, then do nothing.
|
||||||
|
if !self.available {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If we've already blown our budget, then don't waste time looking
|
||||||
|
// for more rare bytes.
|
||||||
|
if self.count > 3 {
|
||||||
|
self.available = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If the pattern is too long, then our offset table is bunk, so
|
||||||
|
// give up.
|
||||||
|
if bytes.len() >= 256 {
|
||||||
|
self.available = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut rarest = match bytes.first() {
|
||||||
|
None => return,
|
||||||
|
Some(&b) => (b, freq_rank(b)),
|
||||||
|
};
|
||||||
|
// The idea here is to look for the rarest byte in each pattern, and
|
||||||
|
// add that to our set. As a special exception, if we see a byte that
|
||||||
|
// we've already added, then we immediately stop and choose that byte,
|
||||||
|
// even if there's another rare byte in the pattern. This helps us
|
||||||
|
// apply the rare byte optimization in more cases by attempting to pick
|
||||||
|
// bytes that are in common between patterns. So for example, if we
|
||||||
|
// were searching for `Sherlock` and `lockjaw`, then this would pick
|
||||||
|
// `k` for both patterns, resulting in the use of `memchr` instead of
|
||||||
|
// `memchr2` for `k` and `j`.
|
||||||
|
let mut found = false;
|
||||||
|
for (pos, &b) in bytes.iter().enumerate() {
|
||||||
|
self.set_offset(pos, b);
|
||||||
|
if found {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.rare_set.contains(b) {
|
||||||
|
found = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let rank = freq_rank(b);
|
||||||
|
if rank < rarest.1 {
|
||||||
|
rarest = (b, rank);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
self.add_rare_byte(rarest.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_offset(&mut self, pos: usize, byte: u8) {
|
||||||
|
// This unwrap is OK because pos is never bigger than our max.
|
||||||
|
let offset = RareByteOffset::new(pos).unwrap();
|
||||||
|
self.byte_offsets.set(byte, offset);
|
||||||
|
if self.ascii_case_insensitive {
|
||||||
|
self.byte_offsets.set(opposite_ascii_case(byte), offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_rare_byte(&mut self, byte: u8) {
|
||||||
|
self.add_one_rare_byte(byte);
|
||||||
|
if self.ascii_case_insensitive {
|
||||||
|
self.add_one_rare_byte(opposite_ascii_case(byte));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_one_rare_byte(&mut self, byte: u8) {
|
||||||
|
if !self.rare_set.contains(byte) {
|
||||||
|
self.rare_set.add(byte);
|
||||||
|
self.count += 1;
|
||||||
|
self.rank_sum += freq_rank(byte) as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A prefilter for scanning for a single "rare" byte.
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct RareBytesOne {
|
||||||
|
byte1: u8,
|
||||||
|
offset: RareByteOffset,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
impl PrefilterI for RareBytesOne {
|
||||||
|
fn find_in(&self, haystack: &[u8], span: Span) -> Candidate {
|
||||||
|
memchr::memchr(self.byte1, &haystack[span])
|
||||||
|
.map(|i| {
|
||||||
|
let pos = span.start + i;
|
||||||
|
cmp::max(
|
||||||
|
span.start,
|
||||||
|
pos.saturating_sub(usize::from(self.offset.max)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map_or(Candidate::None, Candidate::PossibleStartOfMatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A prefilter for scanning for two "rare" bytes.
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct RareBytesTwo {
|
||||||
|
offsets: RareByteOffsets,
|
||||||
|
byte1: u8,
|
||||||
|
byte2: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
impl PrefilterI for RareBytesTwo {
|
||||||
|
fn find_in(&self, haystack: &[u8], span: Span) -> Candidate {
|
||||||
|
memchr::memchr2(self.byte1, self.byte2, &haystack[span])
|
||||||
|
.map(|i| {
|
||||||
|
let pos = span.start + i;
|
||||||
|
let offset = self.offsets.set[usize::from(haystack[pos])].max;
|
||||||
|
cmp::max(span.start, pos.saturating_sub(usize::from(offset)))
|
||||||
|
})
|
||||||
|
.map_or(Candidate::None, Candidate::PossibleStartOfMatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A prefilter for scanning for three "rare" bytes.
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct RareBytesThree {
|
||||||
|
offsets: RareByteOffsets,
|
||||||
|
byte1: u8,
|
||||||
|
byte2: u8,
|
||||||
|
byte3: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
impl PrefilterI for RareBytesThree {
|
||||||
|
fn find_in(&self, haystack: &[u8], span: Span) -> Candidate {
|
||||||
|
memchr::memchr3(self.byte1, self.byte2, self.byte3, &haystack[span])
|
||||||
|
.map(|i| {
|
||||||
|
let pos = span.start + i;
|
||||||
|
let offset = self.offsets.set[usize::from(haystack[pos])].max;
|
||||||
|
cmp::max(span.start, pos.saturating_sub(usize::from(offset)))
|
||||||
|
})
|
||||||
|
.map_or(Candidate::None, Candidate::PossibleStartOfMatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A builder for constructing a starting byte prefilter.
|
||||||
|
///
|
||||||
|
/// A starting byte prefilter is a simplistic prefilter that looks for possible
|
||||||
|
/// matches by reporting all positions corresponding to a particular byte. This
|
||||||
|
/// generally only takes affect when there are at most 3 distinct possible
|
||||||
|
/// starting bytes. e.g., the patterns `foo`, `bar`, and `baz` have two
|
||||||
|
/// distinct starting bytes (`f` and `b`), and this prefilter returns all
|
||||||
|
/// occurrences of either `f` or `b`.
|
||||||
|
///
|
||||||
|
/// In some cases, a heuristic frequency analysis may determine that it would
|
||||||
|
/// be better not to use this prefilter even when there are 3 or fewer distinct
|
||||||
|
/// starting bytes.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct StartBytesBuilder {
|
||||||
|
/// Whether this prefilter should account for ASCII case insensitivity or
|
||||||
|
/// not.
|
||||||
|
ascii_case_insensitive: bool,
|
||||||
|
/// The set of starting bytes observed.
|
||||||
|
byteset: Vec<bool>,
|
||||||
|
/// The number of bytes set to true in `byteset`.
|
||||||
|
count: usize,
|
||||||
|
/// The sum of frequency ranks for the rare bytes detected. This is
|
||||||
|
/// intended to give a heuristic notion of how rare the bytes are.
|
||||||
|
rank_sum: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StartBytesBuilder {
|
||||||
|
/// Create a new builder for constructing a start byte prefilter.
|
||||||
|
fn new() -> StartBytesBuilder {
|
||||||
|
StartBytesBuilder {
|
||||||
|
ascii_case_insensitive: false,
|
||||||
|
byteset: vec![false; 256],
|
||||||
|
count: 0,
|
||||||
|
rank_sum: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable ASCII case insensitivity. When set, byte strings added to this
|
||||||
|
/// builder will be interpreted without respect to ASCII case.
|
||||||
|
fn ascii_case_insensitive(mut self, yes: bool) -> StartBytesBuilder {
|
||||||
|
self.ascii_case_insensitive = yes;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the starting bytes prefilter.
|
||||||
|
///
|
||||||
|
/// If there are more than 3 distinct starting bytes, or if heuristics
|
||||||
|
/// otherwise determine that this prefilter should not be used, then `None`
|
||||||
|
/// is returned.
|
||||||
|
fn build(&self) -> Option<Prefilter> {
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
fn imp(builder: &StartBytesBuilder) -> Option<Prefilter> {
|
||||||
|
if builder.count > 3 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let (mut bytes, mut len) = ([0; 3], 0);
|
||||||
|
for b in 0..256 {
|
||||||
|
if !builder.byteset[b] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// We don't handle non-ASCII bytes for now. Getting non-ASCII
|
||||||
|
// bytes right is trickier, since we generally don't want to put
|
||||||
|
// a leading UTF-8 code unit into a prefilter that isn't ASCII,
|
||||||
|
// since they can frequently. Instead, it would be better to use a
|
||||||
|
// continuation byte, but this requires more sophisticated analysis
|
||||||
|
// of the automaton and a richer prefilter API.
|
||||||
|
if b > 0x7F {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
bytes[len] = b as u8;
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
let finder: Arc<dyn PrefilterI> = match len {
|
||||||
|
0 => return None,
|
||||||
|
1 => Arc::new(StartBytesOne { byte1: bytes[0] }),
|
||||||
|
2 => Arc::new(StartBytesTwo {
|
||||||
|
byte1: bytes[0],
|
||||||
|
byte2: bytes[1],
|
||||||
|
}),
|
||||||
|
3 => Arc::new(StartBytesThree {
|
||||||
|
byte1: bytes[0],
|
||||||
|
byte2: bytes[1],
|
||||||
|
byte3: bytes[2],
|
||||||
|
}),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Some(Prefilter { finder, memory_usage: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "perf-literal"))]
|
||||||
|
fn imp(_: &StartBytesBuilder) -> Option<Prefilter> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
imp(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a byte string to this builder.
|
||||||
|
///
|
||||||
|
/// All patterns added to an Aho-Corasick automaton should be added to this
|
||||||
|
/// builder before attempting to construct the prefilter.
|
||||||
|
fn add(&mut self, bytes: &[u8]) {
|
||||||
|
if self.count > 3 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(&byte) = bytes.first() {
|
||||||
|
self.add_one_byte(byte);
|
||||||
|
if self.ascii_case_insensitive {
|
||||||
|
self.add_one_byte(opposite_ascii_case(byte));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_one_byte(&mut self, byte: u8) {
|
||||||
|
if !self.byteset[byte as usize] {
|
||||||
|
self.byteset[byte as usize] = true;
|
||||||
|
self.count += 1;
|
||||||
|
self.rank_sum += freq_rank(byte) as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A prefilter for scanning for a single starting byte.
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct StartBytesOne {
|
||||||
|
byte1: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
impl PrefilterI for StartBytesOne {
|
||||||
|
fn find_in(&self, haystack: &[u8], span: Span) -> Candidate {
|
||||||
|
memchr::memchr(self.byte1, &haystack[span])
|
||||||
|
.map(|i| span.start + i)
|
||||||
|
.map_or(Candidate::None, Candidate::PossibleStartOfMatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A prefilter for scanning for two starting bytes.
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct StartBytesTwo {
|
||||||
|
byte1: u8,
|
||||||
|
byte2: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
impl PrefilterI for StartBytesTwo {
|
||||||
|
fn find_in(&self, haystack: &[u8], span: Span) -> Candidate {
|
||||||
|
memchr::memchr2(self.byte1, self.byte2, &haystack[span])
|
||||||
|
.map(|i| span.start + i)
|
||||||
|
.map_or(Candidate::None, Candidate::PossibleStartOfMatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A prefilter for scanning for three starting bytes.
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct StartBytesThree {
|
||||||
|
byte1: u8,
|
||||||
|
byte2: u8,
|
||||||
|
byte3: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "perf-literal")]
|
||||||
|
impl PrefilterI for StartBytesThree {
|
||||||
|
fn find_in(&self, haystack: &[u8], span: Span) -> Candidate {
|
||||||
|
memchr::memchr3(self.byte1, self.byte2, self.byte3, &haystack[span])
|
||||||
|
.map(|i| span.start + i)
|
||||||
|
.map_or(Candidate::None, Candidate::PossibleStartOfMatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the given byte is an ASCII letter, then return it in the opposite case.
|
||||||
|
/// e.g., Given `b'A'`, this returns `b'a'`, and given `b'a'`, this returns
|
||||||
|
/// `b'A'`. If a non-ASCII letter is given, then the given byte is returned.
|
||||||
|
pub(crate) fn opposite_ascii_case(b: u8) -> u8 {
|
||||||
|
if b'A' <= b && b <= b'Z' {
|
||||||
|
b.to_ascii_lowercase()
|
||||||
|
} else if b'a' <= b && b <= b'z' {
|
||||||
|
b.to_ascii_uppercase()
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the frequency rank of the given byte. The higher the rank, the more
|
||||||
|
/// common the byte (heuristically speaking).
|
||||||
|
fn freq_rank(b: u8) -> u8 {
|
||||||
|
use crate::util::byte_frequencies::BYTE_FREQUENCIES;
|
||||||
|
BYTE_FREQUENCIES[b as usize]
|
||||||
|
}
|
||||||
759
third_party/crates/aho-corasick-1.1.5/src/util/primitives.rs
vendored
Normal file
759
third_party/crates/aho-corasick-1.1.5/src/util/primitives.rs
vendored
Normal file
@@ -0,0 +1,759 @@
|
|||||||
|
/*!
|
||||||
|
Lower level primitive types that are useful in a variety of circumstances.
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
This list represents the principle types in this module and briefly describes
|
||||||
|
when you might want to use them.
|
||||||
|
|
||||||
|
* [`PatternID`] - A type that represents the identifier of a regex pattern.
|
||||||
|
This is probably the most widely used type in this module (which is why it's
|
||||||
|
also re-exported in the crate root).
|
||||||
|
* [`StateID`] - A type the represents the identifier of a finite automaton
|
||||||
|
state. This is used for both NFAs and DFAs, with the notable exception of
|
||||||
|
the hybrid NFA/DFA. (The hybrid NFA/DFA uses a special purpose "lazy" state
|
||||||
|
identifier.)
|
||||||
|
* [`SmallIndex`] - The internal representation of both a `PatternID` and a
|
||||||
|
`StateID`. Its purpose is to serve as a type that can index memory without
|
||||||
|
being as big as a `usize` on 64-bit targets. The main idea behind this type
|
||||||
|
is that there are many things in regex engines that will, in practice, never
|
||||||
|
overflow a 32-bit integer. (For example, like the number of patterns in a regex
|
||||||
|
or the number of states in an NFA.) Thus, a `SmallIndex` can be used to index
|
||||||
|
memory without peppering `as` casts everywhere. Moreover, it forces callers
|
||||||
|
to handle errors in the case where, somehow, the value would otherwise overflow
|
||||||
|
either a 32-bit integer or a `usize` (e.g., on 16-bit targets).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// The macro we use to define some types below adds methods that we don't
|
||||||
|
// use on some of the types. There isn't much, so we just squash the warning.
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::util::int::{Usize, U16, U32, U64};
|
||||||
|
|
||||||
|
/// A type that represents a "small" index.
|
||||||
|
///
|
||||||
|
/// The main idea of this type is to provide something that can index memory,
|
||||||
|
/// but uses less memory than `usize` on 64-bit systems. Specifically, its
|
||||||
|
/// representation is always a `u32` and has `repr(transparent)` enabled. (So
|
||||||
|
/// it is safe to transmute between a `u32` and a `SmallIndex`.)
|
||||||
|
///
|
||||||
|
/// A small index is typically useful in cases where there is no practical way
|
||||||
|
/// that the index will overflow a 32-bit integer. A good example of this is
|
||||||
|
/// an NFA state. If you could somehow build an NFA with `2^30` states, its
|
||||||
|
/// memory usage would be exorbitant and its runtime execution would be so
|
||||||
|
/// slow as to be completely worthless. Therefore, this crate generally deems
|
||||||
|
/// it acceptable to return an error if it would otherwise build an NFA that
|
||||||
|
/// requires a slice longer than what a 32-bit integer can index. In exchange,
|
||||||
|
/// we can use 32-bit indices instead of 64-bit indices in various places.
|
||||||
|
///
|
||||||
|
/// This type ensures this by providing a constructor that will return an error
|
||||||
|
/// if its argument cannot fit into the type. This makes it much easier to
|
||||||
|
/// handle these sorts of boundary cases that are otherwise extremely subtle.
|
||||||
|
///
|
||||||
|
/// On all targets, this type guarantees that its value will fit in a `u32`,
|
||||||
|
/// `i32`, `usize` and an `isize`. This means that on 16-bit targets, for
|
||||||
|
/// example, this type's maximum value will never overflow an `isize`,
|
||||||
|
/// which means it will never overflow a `i16` even though its internal
|
||||||
|
/// representation is still a `u32`.
|
||||||
|
///
|
||||||
|
/// The purpose for making the type fit into even signed integer types like
|
||||||
|
/// `isize` is to guarantee that the difference between any two small indices
|
||||||
|
/// is itself also a small index. This is useful in certain contexts, e.g.,
|
||||||
|
/// for delta encoding.
|
||||||
|
///
|
||||||
|
/// # Other types
|
||||||
|
///
|
||||||
|
/// The following types wrap `SmallIndex` to provide a more focused use case:
|
||||||
|
///
|
||||||
|
/// * [`PatternID`] is for representing the identifiers of patterns.
|
||||||
|
/// * [`StateID`] is for representing the identifiers of states in finite
|
||||||
|
/// automata. It is used for both NFAs and DFAs.
|
||||||
|
///
|
||||||
|
/// # Representation
|
||||||
|
///
|
||||||
|
/// This type is always represented internally by a `u32` and is marked as
|
||||||
|
/// `repr(transparent)`. Thus, this type always has the same representation as
|
||||||
|
/// a `u32`. It is thus safe to transmute between a `u32` and a `SmallIndex`.
|
||||||
|
///
|
||||||
|
/// # Indexing
|
||||||
|
///
|
||||||
|
/// For convenience, callers may use a `SmallIndex` to index slices.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// While a `SmallIndex` is meant to guarantee that its value fits into `usize`
|
||||||
|
/// without using as much space as a `usize` on all targets, callers must
|
||||||
|
/// not rely on this property for safety. Callers may choose to rely on this
|
||||||
|
/// property for correctness however. For example, creating a `SmallIndex` with
|
||||||
|
/// an invalid value can be done in entirely safe code. This may in turn result
|
||||||
|
/// in panics or silent logical errors.
|
||||||
|
#[derive(
|
||||||
|
Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord,
|
||||||
|
)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub(crate) struct SmallIndex(u32);
|
||||||
|
|
||||||
|
impl SmallIndex {
|
||||||
|
/// The maximum index value.
|
||||||
|
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
|
||||||
|
pub const MAX: SmallIndex =
|
||||||
|
// FIXME: Use as_usize() once const functions in traits are stable.
|
||||||
|
SmallIndex::new_unchecked(core::i32::MAX as usize - 1);
|
||||||
|
|
||||||
|
/// The maximum index value.
|
||||||
|
#[cfg(target_pointer_width = "16")]
|
||||||
|
pub const MAX: SmallIndex =
|
||||||
|
SmallIndex::new_unchecked(core::isize::MAX - 1);
|
||||||
|
|
||||||
|
/// The total number of values that can be represented as a small index.
|
||||||
|
pub const LIMIT: usize = SmallIndex::MAX.as_usize() + 1;
|
||||||
|
|
||||||
|
/// The zero index value.
|
||||||
|
pub const ZERO: SmallIndex = SmallIndex::new_unchecked(0);
|
||||||
|
|
||||||
|
/// The number of bytes that a single small index uses in memory.
|
||||||
|
pub const SIZE: usize = core::mem::size_of::<SmallIndex>();
|
||||||
|
|
||||||
|
/// Create a new small index.
|
||||||
|
///
|
||||||
|
/// If the given index exceeds [`SmallIndex::MAX`], then this returns
|
||||||
|
/// an error.
|
||||||
|
#[inline]
|
||||||
|
pub fn new(index: usize) -> Result<SmallIndex, SmallIndexError> {
|
||||||
|
SmallIndex::try_from(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new small index without checking whether the given value
|
||||||
|
/// exceeds [`SmallIndex::MAX`].
|
||||||
|
///
|
||||||
|
/// Using this routine with an invalid index value will result in
|
||||||
|
/// unspecified behavior, but *not* undefined behavior. In particular, an
|
||||||
|
/// invalid index value is likely to cause panics or possibly even silent
|
||||||
|
/// logical errors.
|
||||||
|
///
|
||||||
|
/// Callers must never rely on a `SmallIndex` to be within a certain range
|
||||||
|
/// for memory safety.
|
||||||
|
#[inline]
|
||||||
|
pub const fn new_unchecked(index: usize) -> SmallIndex {
|
||||||
|
// FIXME: Use as_u32() once const functions in traits are stable.
|
||||||
|
SmallIndex::from_u32_unchecked(index as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new small index from a `u32` without checking whether the
|
||||||
|
/// given value exceeds [`SmallIndex::MAX`].
|
||||||
|
///
|
||||||
|
/// Using this routine with an invalid index value will result in
|
||||||
|
/// unspecified behavior, but *not* undefined behavior. In particular, an
|
||||||
|
/// invalid index value is likely to cause panics or possibly even silent
|
||||||
|
/// logical errors.
|
||||||
|
///
|
||||||
|
/// Callers must never rely on a `SmallIndex` to be within a certain range
|
||||||
|
/// for memory safety.
|
||||||
|
#[inline]
|
||||||
|
pub const fn from_u32_unchecked(index: u32) -> SmallIndex {
|
||||||
|
SmallIndex(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like [`SmallIndex::new`], but panics if the given index is not valid.
|
||||||
|
#[inline]
|
||||||
|
pub fn must(index: usize) -> SmallIndex {
|
||||||
|
SmallIndex::new(index).expect("invalid small index")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return this small index as a `usize`. This is guaranteed to never
|
||||||
|
/// overflow `usize`.
|
||||||
|
#[inline]
|
||||||
|
pub const fn as_usize(&self) -> usize {
|
||||||
|
// FIXME: Use as_usize() once const functions in traits are stable.
|
||||||
|
self.0 as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return this small index as a `u64`. This is guaranteed to never
|
||||||
|
/// overflow.
|
||||||
|
#[inline]
|
||||||
|
pub const fn as_u64(&self) -> u64 {
|
||||||
|
// FIXME: Use u64::from() once const functions in traits are stable.
|
||||||
|
self.0 as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the internal `u32` of this small index. This is guaranteed to
|
||||||
|
/// never overflow `u32`.
|
||||||
|
#[inline]
|
||||||
|
pub const fn as_u32(&self) -> u32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the internal `u32` of this small index represented as an `i32`.
|
||||||
|
/// This is guaranteed to never overflow an `i32`.
|
||||||
|
#[inline]
|
||||||
|
pub const fn as_i32(&self) -> i32 {
|
||||||
|
// This is OK because we guarantee that our max value is <= i32::MAX.
|
||||||
|
self.0 as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns one more than this small index as a usize.
|
||||||
|
///
|
||||||
|
/// Since a small index has constraints on its maximum value, adding `1` to
|
||||||
|
/// it will always fit in a `usize`, `isize`, `u32` and a `i32`.
|
||||||
|
#[inline]
|
||||||
|
pub fn one_more(&self) -> usize {
|
||||||
|
self.as_usize() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode this small index from the bytes given using the native endian
|
||||||
|
/// byte order for the current target.
|
||||||
|
///
|
||||||
|
/// If the decoded integer is not representable as a small index for the
|
||||||
|
/// current target, then this returns an error.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_ne_bytes(
|
||||||
|
bytes: [u8; 4],
|
||||||
|
) -> Result<SmallIndex, SmallIndexError> {
|
||||||
|
let id = u32::from_ne_bytes(bytes);
|
||||||
|
if id > SmallIndex::MAX.as_u32() {
|
||||||
|
return Err(SmallIndexError { attempted: u64::from(id) });
|
||||||
|
}
|
||||||
|
Ok(SmallIndex::new_unchecked(id.as_usize()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode this small index from the bytes given using the native endian
|
||||||
|
/// byte order for the current target.
|
||||||
|
///
|
||||||
|
/// This is analogous to [`SmallIndex::new_unchecked`] in that is does not
|
||||||
|
/// check whether the decoded integer is representable as a small index.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_ne_bytes_unchecked(bytes: [u8; 4]) -> SmallIndex {
|
||||||
|
SmallIndex::new_unchecked(u32::from_ne_bytes(bytes).as_usize())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the underlying small index integer as raw bytes in native endian
|
||||||
|
/// format.
|
||||||
|
#[inline]
|
||||||
|
pub fn to_ne_bytes(&self) -> [u8; 4] {
|
||||||
|
self.0.to_ne_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::ops::Index<SmallIndex> for [T] {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn index(&self, index: SmallIndex) -> &T {
|
||||||
|
&self[index.as_usize()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::ops::IndexMut<SmallIndex> for [T] {
|
||||||
|
#[inline]
|
||||||
|
fn index_mut(&mut self, index: SmallIndex) -> &mut T {
|
||||||
|
&mut self[index.as_usize()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::ops::Index<SmallIndex> for Vec<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn index(&self, index: SmallIndex) -> &T {
|
||||||
|
&self[index.as_usize()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::ops::IndexMut<SmallIndex> for Vec<T> {
|
||||||
|
#[inline]
|
||||||
|
fn index_mut(&mut self, index: SmallIndex) -> &mut T {
|
||||||
|
&mut self[index.as_usize()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StateID> for SmallIndex {
|
||||||
|
fn from(sid: StateID) -> SmallIndex {
|
||||||
|
sid.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PatternID> for SmallIndex {
|
||||||
|
fn from(pid: PatternID) -> SmallIndex {
|
||||||
|
pid.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for SmallIndex {
|
||||||
|
fn from(index: u8) -> SmallIndex {
|
||||||
|
SmallIndex::new_unchecked(usize::from(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u16> for SmallIndex {
|
||||||
|
type Error = SmallIndexError;
|
||||||
|
|
||||||
|
fn try_from(index: u16) -> Result<SmallIndex, SmallIndexError> {
|
||||||
|
if u32::from(index) > SmallIndex::MAX.as_u32() {
|
||||||
|
return Err(SmallIndexError { attempted: u64::from(index) });
|
||||||
|
}
|
||||||
|
Ok(SmallIndex::new_unchecked(index.as_usize()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for SmallIndex {
|
||||||
|
type Error = SmallIndexError;
|
||||||
|
|
||||||
|
fn try_from(index: u32) -> Result<SmallIndex, SmallIndexError> {
|
||||||
|
if index > SmallIndex::MAX.as_u32() {
|
||||||
|
return Err(SmallIndexError { attempted: u64::from(index) });
|
||||||
|
}
|
||||||
|
Ok(SmallIndex::new_unchecked(index.as_usize()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u64> for SmallIndex {
|
||||||
|
type Error = SmallIndexError;
|
||||||
|
|
||||||
|
fn try_from(index: u64) -> Result<SmallIndex, SmallIndexError> {
|
||||||
|
if index > SmallIndex::MAX.as_u64() {
|
||||||
|
return Err(SmallIndexError { attempted: index });
|
||||||
|
}
|
||||||
|
Ok(SmallIndex::new_unchecked(index.as_usize()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<usize> for SmallIndex {
|
||||||
|
type Error = SmallIndexError;
|
||||||
|
|
||||||
|
fn try_from(index: usize) -> Result<SmallIndex, SmallIndexError> {
|
||||||
|
if index > SmallIndex::MAX.as_usize() {
|
||||||
|
return Err(SmallIndexError { attempted: index.as_u64() });
|
||||||
|
}
|
||||||
|
Ok(SmallIndex::new_unchecked(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This error occurs when a small index could not be constructed.
|
||||||
|
///
|
||||||
|
/// This occurs when given an integer exceeding the maximum small index value.
|
||||||
|
///
|
||||||
|
/// When the `std` feature is enabled, this implements the `Error` trait.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct SmallIndexError {
|
||||||
|
attempted: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SmallIndexError {
|
||||||
|
/// Returns the value that could not be converted to a small index.
|
||||||
|
pub fn attempted(&self) -> u64 {
|
||||||
|
self.attempted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for SmallIndexError {}
|
||||||
|
|
||||||
|
impl core::fmt::Display for SmallIndexError {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"failed to create small index from {:?}, which exceeds {:?}",
|
||||||
|
self.attempted(),
|
||||||
|
SmallIndex::MAX,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct SmallIndexIter {
|
||||||
|
rng: core::ops::Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for SmallIndexIter {
|
||||||
|
type Item = SmallIndex;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<SmallIndex> {
|
||||||
|
if self.rng.start >= self.rng.end {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let next_id = self.rng.start + 1;
|
||||||
|
let id = core::mem::replace(&mut self.rng.start, next_id);
|
||||||
|
// new_unchecked is OK since we asserted that the number of
|
||||||
|
// elements in this iterator will fit in an ID at construction.
|
||||||
|
Some(SmallIndex::new_unchecked(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! index_type_impls {
|
||||||
|
($name:ident, $err:ident, $iter:ident, $withiter:ident) => {
|
||||||
|
impl $name {
|
||||||
|
/// The maximum value.
|
||||||
|
pub const MAX: $name = $name(SmallIndex::MAX);
|
||||||
|
|
||||||
|
/// The total number of values that can be represented.
|
||||||
|
pub const LIMIT: usize = SmallIndex::LIMIT;
|
||||||
|
|
||||||
|
/// The zero value.
|
||||||
|
pub const ZERO: $name = $name(SmallIndex::ZERO);
|
||||||
|
|
||||||
|
/// The number of bytes that a single value uses in memory.
|
||||||
|
pub const SIZE: usize = SmallIndex::SIZE;
|
||||||
|
|
||||||
|
/// Create a new value that is represented by a "small index."
|
||||||
|
///
|
||||||
|
/// If the given index exceeds the maximum allowed value, then this
|
||||||
|
/// returns an error.
|
||||||
|
#[inline]
|
||||||
|
pub fn new(value: usize) -> Result<$name, $err> {
|
||||||
|
SmallIndex::new(value).map($name).map_err($err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new value without checking whether the given argument
|
||||||
|
/// exceeds the maximum.
|
||||||
|
///
|
||||||
|
/// Using this routine with an invalid value will result in
|
||||||
|
/// unspecified behavior, but *not* undefined behavior. In
|
||||||
|
/// particular, an invalid ID value is likely to cause panics or
|
||||||
|
/// possibly even silent logical errors.
|
||||||
|
///
|
||||||
|
/// Callers must never rely on this type to be within a certain
|
||||||
|
/// range for memory safety.
|
||||||
|
#[inline]
|
||||||
|
pub const fn new_unchecked(value: usize) -> $name {
|
||||||
|
$name(SmallIndex::new_unchecked(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new value from a `u32` without checking whether the
|
||||||
|
/// given value exceeds the maximum.
|
||||||
|
///
|
||||||
|
/// Using this routine with an invalid value will result in
|
||||||
|
/// unspecified behavior, but *not* undefined behavior. In
|
||||||
|
/// particular, an invalid ID value is likely to cause panics or
|
||||||
|
/// possibly even silent logical errors.
|
||||||
|
///
|
||||||
|
/// Callers must never rely on this type to be within a certain
|
||||||
|
/// range for memory safety.
|
||||||
|
#[inline]
|
||||||
|
pub const fn from_u32_unchecked(index: u32) -> $name {
|
||||||
|
$name(SmallIndex::from_u32_unchecked(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like `new`, but panics if the given value is not valid.
|
||||||
|
#[inline]
|
||||||
|
pub fn must(value: usize) -> $name {
|
||||||
|
$name::new(value).expect(concat!(
|
||||||
|
"invalid ",
|
||||||
|
stringify!($name),
|
||||||
|
" value"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the internal value as a `usize`. This is guaranteed to
|
||||||
|
/// never overflow `usize`.
|
||||||
|
#[inline]
|
||||||
|
pub const fn as_usize(&self) -> usize {
|
||||||
|
self.0.as_usize()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the internal value as a `u64`. This is guaranteed to
|
||||||
|
/// never overflow.
|
||||||
|
#[inline]
|
||||||
|
pub const fn as_u64(&self) -> u64 {
|
||||||
|
self.0.as_u64()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the internal value as a `u32`. This is guaranteed to
|
||||||
|
/// never overflow `u32`.
|
||||||
|
#[inline]
|
||||||
|
pub const fn as_u32(&self) -> u32 {
|
||||||
|
self.0.as_u32()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the internal value as a `i32`. This is guaranteed to
|
||||||
|
/// never overflow an `i32`.
|
||||||
|
#[inline]
|
||||||
|
pub const fn as_i32(&self) -> i32 {
|
||||||
|
self.0.as_i32()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns one more than this value as a usize.
|
||||||
|
///
|
||||||
|
/// Since values represented by a "small index" have constraints
|
||||||
|
/// on their maximum value, adding `1` to it will always fit in a
|
||||||
|
/// `usize`, `u32` and a `i32`.
|
||||||
|
#[inline]
|
||||||
|
pub fn one_more(&self) -> usize {
|
||||||
|
self.0.one_more()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode this value from the bytes given using the native endian
|
||||||
|
/// byte order for the current target.
|
||||||
|
///
|
||||||
|
/// If the decoded integer is not representable as a small index
|
||||||
|
/// for the current target, then this returns an error.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_ne_bytes(bytes: [u8; 4]) -> Result<$name, $err> {
|
||||||
|
SmallIndex::from_ne_bytes(bytes).map($name).map_err($err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode this value from the bytes given using the native endian
|
||||||
|
/// byte order for the current target.
|
||||||
|
///
|
||||||
|
/// This is analogous to `new_unchecked` in that is does not check
|
||||||
|
/// whether the decoded integer is representable as a small index.
|
||||||
|
#[inline]
|
||||||
|
pub fn from_ne_bytes_unchecked(bytes: [u8; 4]) -> $name {
|
||||||
|
$name(SmallIndex::from_ne_bytes_unchecked(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the underlying integer as raw bytes in native endian
|
||||||
|
/// format.
|
||||||
|
#[inline]
|
||||||
|
pub fn to_ne_bytes(&self) -> [u8; 4] {
|
||||||
|
self.0.to_ne_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over all values from 0 up to and not
|
||||||
|
/// including the given length.
|
||||||
|
///
|
||||||
|
/// If the given length exceeds this type's limit, then this
|
||||||
|
/// panics.
|
||||||
|
pub(crate) fn iter(len: usize) -> $iter {
|
||||||
|
$iter::new(len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We write our own Debug impl so that we get things like PatternID(5)
|
||||||
|
// instead of PatternID(SmallIndex(5)).
|
||||||
|
impl core::fmt::Debug for $name {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
f.debug_tuple(stringify!($name)).field(&self.as_u32()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::ops::Index<$name> for [T] {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn index(&self, index: $name) -> &T {
|
||||||
|
&self[index.as_usize()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::ops::IndexMut<$name> for [T] {
|
||||||
|
#[inline]
|
||||||
|
fn index_mut(&mut self, index: $name) -> &mut T {
|
||||||
|
&mut self[index.as_usize()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::ops::Index<$name> for Vec<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn index(&self, index: $name) -> &T {
|
||||||
|
&self[index.as_usize()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::ops::IndexMut<$name> for Vec<T> {
|
||||||
|
#[inline]
|
||||||
|
fn index_mut(&mut self, index: $name) -> &mut T {
|
||||||
|
&mut self[index.as_usize()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SmallIndex> for $name {
|
||||||
|
fn from(index: SmallIndex) -> $name {
|
||||||
|
$name(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for $name {
|
||||||
|
fn from(value: u8) -> $name {
|
||||||
|
$name(SmallIndex::from(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u16> for $name {
|
||||||
|
type Error = $err;
|
||||||
|
|
||||||
|
fn try_from(value: u16) -> Result<$name, $err> {
|
||||||
|
SmallIndex::try_from(value).map($name).map_err($err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for $name {
|
||||||
|
type Error = $err;
|
||||||
|
|
||||||
|
fn try_from(value: u32) -> Result<$name, $err> {
|
||||||
|
SmallIndex::try_from(value).map($name).map_err($err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u64> for $name {
|
||||||
|
type Error = $err;
|
||||||
|
|
||||||
|
fn try_from(value: u64) -> Result<$name, $err> {
|
||||||
|
SmallIndex::try_from(value).map($name).map_err($err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<usize> for $name {
|
||||||
|
type Error = $err;
|
||||||
|
|
||||||
|
fn try_from(value: usize) -> Result<$name, $err> {
|
||||||
|
SmallIndex::try_from(value).map($name).map_err($err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This error occurs when an ID could not be constructed.
|
||||||
|
///
|
||||||
|
/// This occurs when given an integer exceeding the maximum allowed
|
||||||
|
/// value.
|
||||||
|
///
|
||||||
|
/// When the `std` feature is enabled, this implements the `Error`
|
||||||
|
/// trait.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct $err(SmallIndexError);
|
||||||
|
|
||||||
|
impl $err {
|
||||||
|
/// Returns the value that could not be converted to an ID.
|
||||||
|
pub fn attempted(&self) -> u64 {
|
||||||
|
self.0.attempted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
impl std::error::Error for $err {}
|
||||||
|
|
||||||
|
impl core::fmt::Display for $err {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"failed to create {} from {:?}, which exceeds {:?}",
|
||||||
|
stringify!($name),
|
||||||
|
self.attempted(),
|
||||||
|
$name::MAX,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct $iter(SmallIndexIter);
|
||||||
|
|
||||||
|
impl $iter {
|
||||||
|
fn new(len: usize) -> $iter {
|
||||||
|
assert!(
|
||||||
|
len <= $name::LIMIT,
|
||||||
|
"cannot create iterator for {} when number of \
|
||||||
|
elements exceed {:?}",
|
||||||
|
stringify!($name),
|
||||||
|
$name::LIMIT,
|
||||||
|
);
|
||||||
|
$iter(SmallIndexIter { rng: 0..len })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for $iter {
|
||||||
|
type Item = $name;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<$name> {
|
||||||
|
self.0.next().map($name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator adapter that is like std::iter::Enumerate, but attaches
|
||||||
|
/// small index values instead. It requires `ExactSizeIterator`. At
|
||||||
|
/// construction, it ensures that the index of each element in the
|
||||||
|
/// iterator is representable in the corresponding small index type.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct $withiter<I> {
|
||||||
|
it: I,
|
||||||
|
ids: $iter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator + ExactSizeIterator> $withiter<I> {
|
||||||
|
fn new(it: I) -> $withiter<I> {
|
||||||
|
let ids = $name::iter(it.len());
|
||||||
|
$withiter { it, ids }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator + ExactSizeIterator> Iterator for $withiter<I> {
|
||||||
|
type Item = ($name, I::Item);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<($name, I::Item)> {
|
||||||
|
let item = self.it.next()?;
|
||||||
|
// Number of elements in this iterator must match, according
|
||||||
|
// to contract of ExactSizeIterator.
|
||||||
|
let id = self.ids.next().unwrap();
|
||||||
|
Some((id, item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The identifier of a pattern in an Aho-Corasick automaton.
|
||||||
|
///
|
||||||
|
/// It is represented by a `u32` even on 64-bit systems in order to conserve
|
||||||
|
/// space. Namely, on all targets, this type guarantees that its value will
|
||||||
|
/// fit in a `u32`, `i32`, `usize` and an `isize`. This means that on 16-bit
|
||||||
|
/// targets, for example, this type's maximum value will never overflow an
|
||||||
|
/// `isize`, which means it will never overflow a `i16` even though its
|
||||||
|
/// internal representation is still a `u32`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// While a `PatternID` is meant to guarantee that its value fits into `usize`
|
||||||
|
/// without using as much space as a `usize` on all targets, callers must
|
||||||
|
/// not rely on this property for safety. Callers may choose to rely on this
|
||||||
|
/// property for correctness however. For example, creating a `StateID` with an
|
||||||
|
/// invalid value can be done in entirely safe code. This may in turn result in
|
||||||
|
/// panics or silent logical errors.
|
||||||
|
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct PatternID(SmallIndex);
|
||||||
|
|
||||||
|
/// The identifier of a finite automaton state.
|
||||||
|
///
|
||||||
|
/// It is represented by a `u32` even on 64-bit systems in order to conserve
|
||||||
|
/// space. Namely, on all targets, this type guarantees that its value will
|
||||||
|
/// fit in a `u32`, `i32`, `usize` and an `isize`. This means that on 16-bit
|
||||||
|
/// targets, for example, this type's maximum value will never overflow an
|
||||||
|
/// `isize`, which means it will never overflow a `i16` even though its
|
||||||
|
/// internal representation is still a `u32`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// While a `StateID` is meant to guarantee that its value fits into `usize`
|
||||||
|
/// without using as much space as a `usize` on all targets, callers must
|
||||||
|
/// not rely on this property for safety. Callers may choose to rely on this
|
||||||
|
/// property for correctness however. For example, creating a `StateID` with an
|
||||||
|
/// invalid value can be done in entirely safe code. This may in turn result in
|
||||||
|
/// panics or silent logical errors.
|
||||||
|
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct StateID(SmallIndex);
|
||||||
|
|
||||||
|
index_type_impls!(PatternID, PatternIDError, PatternIDIter, WithPatternIDIter);
|
||||||
|
index_type_impls!(StateID, StateIDError, StateIDIter, WithStateIDIter);
|
||||||
|
|
||||||
|
/// A utility trait that defines a couple of adapters for making it convenient
|
||||||
|
/// to access indices as "small index" types. We require ExactSizeIterator so
|
||||||
|
/// that iterator construction can do a single check to make sure the index of
|
||||||
|
/// each element is representable by its small index type.
|
||||||
|
pub(crate) trait IteratorIndexExt: Iterator {
|
||||||
|
fn with_pattern_ids(self) -> WithPatternIDIter<Self>
|
||||||
|
where
|
||||||
|
Self: Sized + ExactSizeIterator,
|
||||||
|
{
|
||||||
|
WithPatternIDIter::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_state_ids(self) -> WithStateIDIter<Self>
|
||||||
|
where
|
||||||
|
Self: Sized + ExactSizeIterator,
|
||||||
|
{
|
||||||
|
WithStateIDIter::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator> IteratorIndexExt for I {}
|
||||||
214
third_party/crates/aho-corasick-1.1.5/src/util/remapper.rs
vendored
Normal file
214
third_party/crates/aho-corasick-1.1.5/src/util/remapper.rs
vendored
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::{nfa::noncontiguous, util::primitives::StateID};
|
||||||
|
|
||||||
|
/// Remappable is a tightly coupled abstraction that facilitates remapping
|
||||||
|
/// state identifiers in DFAs.
|
||||||
|
///
|
||||||
|
/// The main idea behind remapping state IDs is that DFAs often need to check
|
||||||
|
/// if a certain state is a "special" state of some kind (like a match state)
|
||||||
|
/// during a search. Since this is extremely perf critical code, we want this
|
||||||
|
/// check to be as fast as possible. Partitioning state IDs into, for example,
|
||||||
|
/// into "non-match" and "match" states means one can tell if a state is a
|
||||||
|
/// match state via a simple comparison of the state ID.
|
||||||
|
///
|
||||||
|
/// The issue is that during the DFA construction process, it's not
|
||||||
|
/// particularly easy to partition the states. Instead, the simplest thing is
|
||||||
|
/// to often just do a pass over all of the states and shuffle them into their
|
||||||
|
/// desired partitionings. To do that, we need a mechanism for swapping states.
|
||||||
|
/// Hence, this abstraction.
|
||||||
|
///
|
||||||
|
/// Normally, for such little code, I would just duplicate it. But this is a
|
||||||
|
/// key optimization and the implementation is a bit subtle. So the abstraction
|
||||||
|
/// is basically a ham-fisted attempt at DRY. The only place we use this is in
|
||||||
|
/// the dense and one-pass DFAs.
|
||||||
|
///
|
||||||
|
/// See also src/dfa/special.rs for a more detailed explanation of how dense
|
||||||
|
/// DFAs are partitioned.
|
||||||
|
pub(crate) trait Remappable: core::fmt::Debug {
|
||||||
|
/// Return the total number of states.
|
||||||
|
fn state_len(&self) -> usize;
|
||||||
|
|
||||||
|
/// Swap the states pointed to by the given IDs. The underlying finite
|
||||||
|
/// state machine should be mutated such that all of the transitions in
|
||||||
|
/// `id1` are now in the memory region where the transitions for `id2`
|
||||||
|
/// were, and all of the transitions in `id2` are now in the memory region
|
||||||
|
/// where the transitions for `id1` were.
|
||||||
|
///
|
||||||
|
/// Essentially, this "moves" `id1` to `id2` and `id2` to `id1`.
|
||||||
|
///
|
||||||
|
/// It is expected that, after calling this, the underlying state machine
|
||||||
|
/// will be left in an inconsistent state, since any other transitions
|
||||||
|
/// pointing to, e.g., `id1` need to be updated to point to `id2`, since
|
||||||
|
/// that's where `id1` moved to.
|
||||||
|
///
|
||||||
|
/// In order to "fix" the underlying inconsistent state, a `Remapper`
|
||||||
|
/// should be used to guarantee that `remap` is called at the appropriate
|
||||||
|
/// time.
|
||||||
|
fn swap_states(&mut self, id1: StateID, id2: StateID);
|
||||||
|
|
||||||
|
/// This must remap every single state ID in the underlying value according
|
||||||
|
/// to the function given. For example, in a DFA, this should remap every
|
||||||
|
/// transition and every starting state ID.
|
||||||
|
fn remap(&mut self, map: impl Fn(StateID) -> StateID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remapper is an abstraction the manages the remapping of state IDs in a
|
||||||
|
/// finite state machine. This is useful when one wants to shuffle states into
|
||||||
|
/// different positions in the machine.
|
||||||
|
///
|
||||||
|
/// One of the key complexities this manages is the ability to correctly move
|
||||||
|
/// one state multiple times.
|
||||||
|
///
|
||||||
|
/// Once shuffling is complete, `remap` must be called, which will rewrite
|
||||||
|
/// all pertinent transitions to updated state IDs. Neglecting to call `remap`
|
||||||
|
/// will almost certainly result in a corrupt machine.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Remapper {
|
||||||
|
/// A map from the index of a state to its pre-multiplied identifier.
|
||||||
|
///
|
||||||
|
/// When a state is swapped with another, then their corresponding
|
||||||
|
/// locations in this map are also swapped. Thus, its new position will
|
||||||
|
/// still point to its old pre-multiplied StateID.
|
||||||
|
///
|
||||||
|
/// While there is a bit more to it, this then allows us to rewrite the
|
||||||
|
/// state IDs in a DFA's transition table in a single pass. This is done
|
||||||
|
/// by iterating over every ID in this map, then iterating over each
|
||||||
|
/// transition for the state at that ID and re-mapping the transition from
|
||||||
|
/// `old_id` to `map[dfa.to_index(old_id)]`. That is, we find the position
|
||||||
|
/// in this map where `old_id` *started*, and set it to where it ended up
|
||||||
|
/// after all swaps have been completed.
|
||||||
|
map: Vec<StateID>,
|
||||||
|
/// A way to map indices to state IDs (and back).
|
||||||
|
idx: IndexMapper,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Remapper {
|
||||||
|
/// Create a new remapper from the given remappable implementation. The
|
||||||
|
/// remapper can then be used to swap states. The remappable value given
|
||||||
|
/// here must the same one given to `swap` and `remap`.
|
||||||
|
///
|
||||||
|
/// The given stride should be the stride of the transition table expressed
|
||||||
|
/// as a power of 2. This stride is used to map between state IDs and state
|
||||||
|
/// indices. If state IDs and state indices are equivalent, then provide
|
||||||
|
/// a `stride2` of `0`, which acts as an identity.
|
||||||
|
pub(crate) fn new(r: &impl Remappable, stride2: usize) -> Remapper {
|
||||||
|
let idx = IndexMapper { stride2 };
|
||||||
|
let map = (0..r.state_len()).map(|i| idx.to_state_id(i)).collect();
|
||||||
|
Remapper { map, idx }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Swap two states. Once this is called, callers must follow through to
|
||||||
|
/// call `remap`, or else it's possible for the underlying remappable
|
||||||
|
/// value to be in a corrupt state.
|
||||||
|
pub(crate) fn swap(
|
||||||
|
&mut self,
|
||||||
|
r: &mut impl Remappable,
|
||||||
|
id1: StateID,
|
||||||
|
id2: StateID,
|
||||||
|
) {
|
||||||
|
if id1 == id2 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r.swap_states(id1, id2);
|
||||||
|
self.map.swap(self.idx.to_index(id1), self.idx.to_index(id2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Complete the remapping process by rewriting all state IDs in the
|
||||||
|
/// remappable value according to the swaps performed.
|
||||||
|
pub(crate) fn remap(mut self, r: &mut impl Remappable) {
|
||||||
|
// Update the map to account for states that have been swapped
|
||||||
|
// multiple times. For example, if (A, C) and (C, G) are swapped, then
|
||||||
|
// transitions previously pointing to A should now point to G. But if
|
||||||
|
// we don't update our map, they will erroneously be set to C. All we
|
||||||
|
// do is follow the swaps in our map until we see our original state
|
||||||
|
// ID.
|
||||||
|
//
|
||||||
|
// The intuition here is to think about how changes are made to the
|
||||||
|
// map: only through pairwise swaps. That means that starting at any
|
||||||
|
// given state, it is always possible to find the loop back to that
|
||||||
|
// state by following the swaps represented in the map (which might be
|
||||||
|
// 0 swaps).
|
||||||
|
//
|
||||||
|
// We are also careful to clone the map before starting in order to
|
||||||
|
// freeze it. We use the frozen map to find our loops, since we need to
|
||||||
|
// update our map as well. Without freezing it, our updates could break
|
||||||
|
// the loops referenced above and produce incorrect results.
|
||||||
|
let oldmap = self.map.clone();
|
||||||
|
for i in 0..r.state_len() {
|
||||||
|
let cur_id = self.idx.to_state_id(i);
|
||||||
|
let mut new_id = oldmap[i];
|
||||||
|
if cur_id == new_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
let id = oldmap[self.idx.to_index(new_id)];
|
||||||
|
if cur_id == id {
|
||||||
|
self.map[i] = new_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
new_id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.remap(|sid| self.map[self.idx.to_index(sid)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple type for mapping between state indices and state IDs.
|
||||||
|
///
|
||||||
|
/// The reason why this exists is because state IDs are "premultiplied" in a
|
||||||
|
/// DFA. That is, in order to get to the transitions for a particular state,
|
||||||
|
/// one need only use the state ID as-is, instead of having to multiply it by
|
||||||
|
/// transition table's stride.
|
||||||
|
///
|
||||||
|
/// The downside of this is that it's inconvenient to map between state IDs
|
||||||
|
/// using a dense map, e.g., Vec<StateID>. That's because state IDs look like
|
||||||
|
/// `0`, `stride`, `2*stride`, `3*stride`, etc., instead of `0`, `1`, `2`, `3`,
|
||||||
|
/// etc.
|
||||||
|
///
|
||||||
|
/// Since our state IDs are premultiplied, we can convert back-and-forth
|
||||||
|
/// between IDs and indices by simply unmultiplying the IDs and multiplying the
|
||||||
|
/// indices.
|
||||||
|
///
|
||||||
|
/// Note that for a sparse NFA, state IDs and indices are equivalent. In this
|
||||||
|
/// case, we set the stride of the index mapped to be `0`, which acts as an
|
||||||
|
/// identity.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct IndexMapper {
|
||||||
|
/// The power of 2 corresponding to the stride of the corresponding
|
||||||
|
/// transition table. 'id >> stride2' de-multiplies an ID while 'index <<
|
||||||
|
/// stride2' pre-multiplies an index to an ID.
|
||||||
|
stride2: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMapper {
|
||||||
|
/// Convert a state ID to a state index.
|
||||||
|
fn to_index(&self, id: StateID) -> usize {
|
||||||
|
id.as_usize() >> self.stride2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a state index to a state ID.
|
||||||
|
fn to_state_id(&self, index: usize) -> StateID {
|
||||||
|
// CORRECTNESS: If the given index is not valid, then it is not
|
||||||
|
// required for this to panic or return a valid state ID. We'll "just"
|
||||||
|
// wind up with panics or silent logic errors at some other point. But
|
||||||
|
// this is OK because if Remappable::state_len is correct and so is
|
||||||
|
// 'to_index', then all inputs to 'to_state_id' should be valid indices
|
||||||
|
// and thus transform into valid state IDs.
|
||||||
|
StateID::new_unchecked(index << self.stride2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Remappable for noncontiguous::NFA {
|
||||||
|
fn state_len(&self) -> usize {
|
||||||
|
noncontiguous::NFA::states(self).len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap_states(&mut self, id1: StateID, id2: StateID) {
|
||||||
|
noncontiguous::NFA::swap_states(self, id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remap(&mut self, map: impl Fn(StateID) -> StateID) {
|
||||||
|
noncontiguous::NFA::remap(self, map)
|
||||||
|
}
|
||||||
|
}
|
||||||
1158
third_party/crates/aho-corasick-1.1.5/src/util/search.rs
vendored
Normal file
1158
third_party/crates/aho-corasick-1.1.5/src/util/search.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
42
third_party/crates/aho-corasick-1.1.5/src/util/special.rs
vendored
Normal file
42
third_party/crates/aho-corasick-1.1.5/src/util/special.rs
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use crate::util::primitives::StateID;
|
||||||
|
|
||||||
|
/// A collection of sentinel state IDs for Aho-Corasick automata.
|
||||||
|
///
|
||||||
|
/// This specifically enables the technique by which we determine which states
|
||||||
|
/// are dead, matches or start states. Namely, by arranging states in a
|
||||||
|
/// particular order, we can determine the type of a state simply by looking at
|
||||||
|
/// its ID.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Special {
|
||||||
|
/// The maximum ID of all the "special" states. This corresponds either to
|
||||||
|
/// start_anchored_id when a prefilter is active and max_match_id when a
|
||||||
|
/// prefilter is not active. The idea here is that if there is no prefilter,
|
||||||
|
/// then there is no point in treating start states as special.
|
||||||
|
pub(crate) max_special_id: StateID,
|
||||||
|
/// The maximum ID of all the match states. Any state ID bigger than this
|
||||||
|
/// is guaranteed to be a non-match ID.
|
||||||
|
///
|
||||||
|
/// It is possible and legal for max_match_id to be equal to
|
||||||
|
/// start_anchored_id, which occurs precisely in the case where the empty
|
||||||
|
/// string is a pattern that was added to the underlying automaton.
|
||||||
|
pub(crate) max_match_id: StateID,
|
||||||
|
/// The state ID of the start state used for unanchored searches.
|
||||||
|
pub(crate) start_unanchored_id: StateID,
|
||||||
|
/// The state ID of the start state used for anchored searches. This is
|
||||||
|
/// always start_unanchored_id+1.
|
||||||
|
pub(crate) start_anchored_id: StateID,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Special {
|
||||||
|
/// Create a new set of "special" state IDs with all IDs initialized to
|
||||||
|
/// zero. The general idea here is that they will be updated and set to
|
||||||
|
/// correct values later.
|
||||||
|
pub(crate) fn zero() -> Special {
|
||||||
|
Special {
|
||||||
|
max_special_id: StateID::ZERO,
|
||||||
|
max_match_id: StateID::ZERO,
|
||||||
|
start_unanchored_id: StateID::ZERO,
|
||||||
|
start_anchored_id: StateID::ZERO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
third_party/crates/autocfg-1.5.1/.cargo-checksum.json
vendored
Normal file
1
third_party/crates/autocfg-1.5.1/.cargo-checksum.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{".cargo_vcs_info.json":"5713fb5e9d7124071a4fb0d0f8f80535560c7cef1995c42fe168af1c2627a050","Cargo.lock":"dc8d767ba9ab277c030476ca736afce4ecb4999a01bd3db8c8210a12ef79e81c","Cargo.toml":"ed7c832b4250c3be736e040d68688c2b83a5b164a93ef533e61532cbe0d44e99","Cargo.toml.orig":"57d2aec0d4157bd9446f97705aa2acff86cc3d1883f470526fed089e7cf9754e","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"27995d58ad5c1145c1a8cd86244ce844886958a35eb2b78c6b772748669999ac","README.md":"1c6c43f1d22ab1c6411b82862c356c550947f3cecbb4c7eede5edefc8890ff32","examples/integers.rs":"589ff4271566dfa322becddf3e2c7b592e6e0bc97b02892ce75619b7e452e930","examples/nightly.rs":"ac8b5a9aa1e04465e44f5053b3c899b635e07af058c73aa8b45176bf4b5912f9","examples/paths.rs":"1b30e466b824ce8df7ad0a55334424131d9d2573d6cf9f7d5d50c09c8901d526","examples/traits.rs":"cbee6a3e1f7db60b02ae25b714926517144a77cb492021f492774cf0e1865a9e","examples/versions.rs":"38535e6d9f5bfae0de474a3db79a40e8f5da8ba9334c5ff4c363de9bc99d4d12","src/error.rs":"fd8ff67c64f7cd1b9f81325a81de4baa34c39d6ae298bdb33f9829cc91acac39","src/lib.rs":"772630d0e09d06f343fd72284c8330eacde87d2a4ee22f70af62a0e2119fbf05","src/rustc.rs":"a8a213ddb64a05c1a1af933bcb331a98879e942b167c33d8f94f9f60ebb14e29","src/tests.rs":"b39f4d880ad343e65307a9e0c381954ea27adce4732f825516ce7952e2e5a91d","src/version.rs":"4f7d23b36f01c7be1871be86c038d6cb4689e145d67c82d3793690e9aa05b133","tests/no_std.rs":"18859dc4992fe1769887bde05f03d28f1ce524eafd17646d3fbcb4379422761a","tests/rustflags.rs":"e8ded4d57ba25379a38ab48456d67df14f82abbbb5f6bb66221c6decbcb517a3","tests/support/mod.rs":"32087d365b438ac3f62df9bb066d8d648b80cb130a5c777afcb2f21fbb68d88e","tests/tests.rs":"39149fe69a45f577eeb531587a5e600d8bc9f8d937480172dac9402939d856f0","tests/wrappers.rs":"96aee2217fbfc7a3aff16e70609c885620a4704dfd708de8b2d0bad4a8c6247d"},"package":"f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"}
|
||||||
6
third_party/crates/autocfg-1.5.1/.cargo_vcs_info.json
vendored
Normal file
6
third_party/crates/autocfg-1.5.1/.cargo_vcs_info.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"git": {
|
||||||
|
"sha1": "2799b09c24e6632f8e653c5cd8fc303e85a906ba"
|
||||||
|
},
|
||||||
|
"path_in_vcs": ""
|
||||||
|
}
|
||||||
110
third_party/crates/autocfg-1.5.1/BUILD.bazel
vendored
Normal file
110
third_party/crates/autocfg-1.5.1/BUILD.bazel
vendored
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
###############################################################################
|
||||||
|
# @generated
|
||||||
|
# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To
|
||||||
|
# regenerate this file, run the following:
|
||||||
|
#
|
||||||
|
# bazel run @@//third_party:vendor_crates
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
load("@rules_rust//cargo:defs.bzl", "cargo_toml_env_vars")
|
||||||
|
load("@rules_rust//rust:defs.bzl", "rust_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
cargo_toml_env_vars(
|
||||||
|
name = "cargo_toml_env_vars",
|
||||||
|
src = "Cargo.toml",
|
||||||
|
)
|
||||||
|
|
||||||
|
rust_library(
|
||||||
|
name = "autocfg",
|
||||||
|
srcs = glob(
|
||||||
|
include = ["**/*.rs"],
|
||||||
|
allow_empty = False,
|
||||||
|
),
|
||||||
|
compile_data = glob(
|
||||||
|
include = ["**"],
|
||||||
|
allow_empty = True,
|
||||||
|
exclude = [
|
||||||
|
"**/* *",
|
||||||
|
".tmp_git_root/**/*",
|
||||||
|
"BUILD",
|
||||||
|
"BUILD.bazel",
|
||||||
|
"WORKSPACE",
|
||||||
|
"WORKSPACE.bazel",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
crate_root = "src/lib.rs",
|
||||||
|
edition = "2015",
|
||||||
|
rustc_env_files = [
|
||||||
|
":cargo_toml_env_vars",
|
||||||
|
],
|
||||||
|
rustc_flags = [
|
||||||
|
"--cap-lints=allow",
|
||||||
|
],
|
||||||
|
tags = [
|
||||||
|
"cargo-bazel",
|
||||||
|
"crate-name=autocfg",
|
||||||
|
"manual",
|
||||||
|
"noclippy",
|
||||||
|
"norustfmt",
|
||||||
|
],
|
||||||
|
target_compatible_with = select({
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-darwin": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-ios": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-ios-macabi": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-ios-sim": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-linux-android": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-pc-windows-msvc": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-fuchsia": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-nixos-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-none": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-nto-qnx710": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-uefi": [],
|
||||||
|
"@rules_rust//rust/platform:arm-unknown-linux-gnueabi": [],
|
||||||
|
"@rules_rust//rust/platform:arm-unknown-linux-musleabi": [],
|
||||||
|
"@rules_rust//rust/platform:armv7-linux-androideabi": [],
|
||||||
|
"@rules_rust//rust/platform:armv7-unknown-linux-gnueabi": [],
|
||||||
|
"@rules_rust//rust/platform:i686-apple-darwin": [],
|
||||||
|
"@rules_rust//rust/platform:i686-linux-android": [],
|
||||||
|
"@rules_rust//rust/platform:i686-pc-windows-msvc": [],
|
||||||
|
"@rules_rust//rust/platform:i686-unknown-freebsd": [],
|
||||||
|
"@rules_rust//rust/platform:i686-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:loongarch64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:mips-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:powerpc-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:riscv32imac-unknown-none-elf": [],
|
||||||
|
"@rules_rust//rust/platform:riscv32imc-unknown-none-elf": [],
|
||||||
|
"@rules_rust//rust/platform:riscv64gc-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:riscv64gc-unknown-none-elf": [],
|
||||||
|
"@rules_rust//rust/platform:s390x-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:sparc64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:sparc64-unknown-netbsd": [],
|
||||||
|
"@rules_rust//rust/platform:sparc64-unknown-openbsd": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv6m-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv7em-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv7em-none-eabihf": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv7m-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv8m.main-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv8m.main-none-eabihf": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-unknown-emscripten": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-unknown-unknown": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-wasip1": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-wasip1-threads": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-wasip2": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-apple-darwin": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-apple-ios": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-apple-ios-macabi": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-linux-android": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-pc-windows-msvc": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-freebsd": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-fuchsia": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-none": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-uefi": [],
|
||||||
|
"//conditions:default": ["@platforms//:incompatible"],
|
||||||
|
}),
|
||||||
|
version = "1.5.1",
|
||||||
|
)
|
||||||
6
third_party/crates/autocfg-1.5.1/Cargo.lock
generated
vendored
Normal file
6
third_party/crates/autocfg-1.5.1/Cargo.lock
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
|
||||||
75
third_party/crates/autocfg-1.5.1/Cargo.toml
vendored
Normal file
75
third_party/crates/autocfg-1.5.1/Cargo.toml
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||||
|
#
|
||||||
|
# When uploading crates to the registry Cargo will automatically
|
||||||
|
# "normalize" Cargo.toml files for maximal compatibility
|
||||||
|
# with all versions of Cargo and also rewrite `path` dependencies
|
||||||
|
# to registry (e.g., crates.io) dependencies.
|
||||||
|
#
|
||||||
|
# If you are reading this file be aware that the original Cargo.toml
|
||||||
|
# will likely look very different (and much more reasonable).
|
||||||
|
# See Cargo.toml.orig for the original contents.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
rust-version = "1.0"
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
authors = ["Josh Stone <cuviper@gmail.com>"]
|
||||||
|
build = false
|
||||||
|
exclude = ["/.github/**"]
|
||||||
|
autolib = false
|
||||||
|
autobins = false
|
||||||
|
autoexamples = false
|
||||||
|
autotests = false
|
||||||
|
autobenches = false
|
||||||
|
description = "Automatic cfg for Rust compiler features"
|
||||||
|
documentation = "https://docs.rs/autocfg/"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = [
|
||||||
|
"rustc",
|
||||||
|
"build",
|
||||||
|
"autoconf",
|
||||||
|
]
|
||||||
|
categories = ["development-tools::build-utils"]
|
||||||
|
license = "Apache-2.0 OR MIT"
|
||||||
|
repository = "https://github.com/cuviper/autocfg"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "autocfg"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "integers"
|
||||||
|
path = "examples/integers.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "nightly"
|
||||||
|
path = "examples/nightly.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "paths"
|
||||||
|
path = "examples/paths.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "traits"
|
||||||
|
path = "examples/traits.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "versions"
|
||||||
|
path = "examples/versions.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "no_std"
|
||||||
|
path = "tests/no_std.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "rustflags"
|
||||||
|
path = "tests/rustflags.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "tests"
|
||||||
|
path = "tests/tests.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "wrappers"
|
||||||
|
path = "tests/wrappers.rs"
|
||||||
|
harness = false
|
||||||
26
third_party/crates/autocfg-1.5.1/Cargo.toml.orig
generated
vendored
Normal file
26
third_party/crates/autocfg-1.5.1/Cargo.toml.orig
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
authors = ["Josh Stone <cuviper@gmail.com>"]
|
||||||
|
license = "Apache-2.0 OR MIT"
|
||||||
|
repository = "https://github.com/cuviper/autocfg"
|
||||||
|
documentation = "https://docs.rs/autocfg/"
|
||||||
|
description = "Automatic cfg for Rust compiler features"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["rustc", "build", "autoconf"]
|
||||||
|
categories = ["development-tools::build-utils"]
|
||||||
|
exclude = ["/.github/**"]
|
||||||
|
rust-version = "1.0"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "no_std"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "rustflags"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "tests"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "wrappers"
|
||||||
|
harness = false
|
||||||
201
third_party/crates/autocfg-1.5.1/LICENSE-APACHE
vendored
Normal file
201
third_party/crates/autocfg-1.5.1/LICENSE-APACHE
vendored
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
25
third_party/crates/autocfg-1.5.1/LICENSE-MIT
vendored
Normal file
25
third_party/crates/autocfg-1.5.1/LICENSE-MIT
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2018 Josh Stone
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
121
third_party/crates/autocfg-1.5.1/README.md
vendored
Normal file
121
third_party/crates/autocfg-1.5.1/README.md
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
autocfg
|
||||||
|
=======
|
||||||
|
|
||||||
|
[](https://crates.io/crates/autocfg)
|
||||||
|
[](https://docs.rs/autocfg)
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
A Rust library for build scripts to automatically configure code based on
|
||||||
|
compiler support. Code snippets are dynamically tested to see if the `rustc`
|
||||||
|
will accept them, rather than hard-coding specific version support.
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add this to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[build-dependencies]
|
||||||
|
autocfg = "1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use it in your `build.rs` script to detect compiler features. For
|
||||||
|
example, to test for 128-bit integer support, it might look like:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate autocfg;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let ac = autocfg::new();
|
||||||
|
ac.emit_has_type("i128");
|
||||||
|
|
||||||
|
// (optional) We don't need to rerun for anything external.
|
||||||
|
autocfg::rerun_path("build.rs");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the type test succeeds, this will write a `cargo:rustc-cfg=has_i128` line
|
||||||
|
for Cargo, which translates to Rust arguments `--cfg has_i128`. Then in the
|
||||||
|
rest of your Rust code, you can add `#[cfg(has_i128)]` conditions on code that
|
||||||
|
should only be used when the compiler supports it.
|
||||||
|
|
||||||
|
|
||||||
|
## Release Notes
|
||||||
|
|
||||||
|
- 1.5.1 (2026-05-22)
|
||||||
|
|
||||||
|
- Refactor the `test_wrappers` test without any shell script.
|
||||||
|
|
||||||
|
- 1.5.0 (2025-06-17)
|
||||||
|
|
||||||
|
- Add `edition` and `set_edition` to control the Rust edition used in probes.
|
||||||
|
- Remove probe result files so they don't pollute the output directory.
|
||||||
|
|
||||||
|
- 1.4.0 (2024-09-26)
|
||||||
|
|
||||||
|
- Add `emit_possibility` for Rust 1.80's [checked cfgs], and call that
|
||||||
|
automatically for methods that conditionally `emit`, by @Techcable.
|
||||||
|
|
||||||
|
[checked cfgs]: https://blog.rust-lang.org/2024/05/06/check-cfg.html
|
||||||
|
|
||||||
|
- 1.3.0 (2024-05-03)
|
||||||
|
|
||||||
|
- Add `probe_raw` for direct control of the code that will be test-compiled.
|
||||||
|
- Use wrappers when querying the `rustc` version information too.
|
||||||
|
|
||||||
|
- 1.2.0 (2024-03-25)
|
||||||
|
|
||||||
|
- Add `no_std` and `set_no_std` to control the use of `#![no_std]` in probes.
|
||||||
|
- Use `RUSTC_WRAPPER` and `RUSTC_WORKSPACE_WRAPPER` when they are set.
|
||||||
|
|
||||||
|
- 1.1.0 (2022-02-07)
|
||||||
|
- Use `CARGO_ENCODED_RUSTFLAGS` when it is set.
|
||||||
|
|
||||||
|
- 1.0.1 (2020-08-20)
|
||||||
|
- Apply `RUSTFLAGS` for more `--target` scenarios, by @adamreichold.
|
||||||
|
|
||||||
|
- 1.0.0 (2020-01-08)
|
||||||
|
- 🎉 Release 1.0! 🎉 (no breaking changes)
|
||||||
|
- Add `probe_expression` and `emit_expression_cfg` to test arbitrary expressions.
|
||||||
|
- Add `probe_constant` and `emit_constant_cfg` to test arbitrary constant expressions.
|
||||||
|
|
||||||
|
- 0.1.7 (2019-10-20)
|
||||||
|
- Apply `RUSTFLAGS` when probing `$TARGET != $HOST`, mainly for sysroot, by @roblabla.
|
||||||
|
|
||||||
|
- 0.1.6 (2019-08-19)
|
||||||
|
- Add `probe`/`emit_sysroot_crate`, by @leo60228.
|
||||||
|
|
||||||
|
- 0.1.5 (2019-07-16)
|
||||||
|
- Mask some warnings from newer rustc.
|
||||||
|
|
||||||
|
- 0.1.4 (2019-05-22)
|
||||||
|
- Relax `std`/`no_std` probing to a warning instead of an error.
|
||||||
|
- Improve `rustc` bootstrap compatibility.
|
||||||
|
|
||||||
|
- 0.1.3 (2019-05-21)
|
||||||
|
- Auto-detects if `#![no_std]` is needed for the `$TARGET`.
|
||||||
|
|
||||||
|
- 0.1.2 (2019-01-16)
|
||||||
|
- Add `rerun_env(ENV)` to print `cargo:rerun-if-env-changed=ENV`.
|
||||||
|
- Add `rerun_path(PATH)` to print `cargo:rerun-if-changed=PATH`.
|
||||||
|
|
||||||
|
|
||||||
|
## Minimum Rust version policy
|
||||||
|
|
||||||
|
This crate's minimum supported `rustc` version is `1.0.0`. Compatibility is
|
||||||
|
its entire reason for existence, so this crate will be extremely conservative
|
||||||
|
about raising this requirement. If this is ever deemed necessary, it will be
|
||||||
|
treated as a major breaking change for semver purposes.
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under either of
|
||||||
|
|
||||||
|
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
|
https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
at your option.
|
||||||
9
third_party/crates/autocfg-1.5.1/examples/integers.rs
vendored
Normal file
9
third_party/crates/autocfg-1.5.1/examples/integers.rs
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
extern crate autocfg;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Normally, cargo will set `OUT_DIR` for build scripts.
|
||||||
|
let ac = autocfg::AutoCfg::with_dir("target").unwrap();
|
||||||
|
for i in 3..8 {
|
||||||
|
ac.emit_has_type(&format!("i{}", 1 << i));
|
||||||
|
}
|
||||||
|
}
|
||||||
18
third_party/crates/autocfg-1.5.1/examples/nightly.rs
vendored
Normal file
18
third_party/crates/autocfg-1.5.1/examples/nightly.rs
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
extern crate autocfg;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Normally, cargo will set `OUT_DIR` for build scripts.
|
||||||
|
let ac = autocfg::AutoCfg::with_dir("target").unwrap();
|
||||||
|
|
||||||
|
// When this feature was stabilized, it also renamed the method to
|
||||||
|
// `chunk_by`, so it's important to *use* the feature in your probe.
|
||||||
|
let code = r#"
|
||||||
|
#![feature(slice_group_by)]
|
||||||
|
pub fn probe(slice: &[i32]) -> impl Iterator<Item = &[i32]> {
|
||||||
|
slice.group_by(|a, b| a == b)
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
if ac.probe_raw(code).is_ok() {
|
||||||
|
autocfg::emit("has_slice_group_by");
|
||||||
|
}
|
||||||
|
}
|
||||||
22
third_party/crates/autocfg-1.5.1/examples/paths.rs
vendored
Normal file
22
third_party/crates/autocfg-1.5.1/examples/paths.rs
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
extern crate autocfg;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Normally, cargo will set `OUT_DIR` for build scripts.
|
||||||
|
let ac = autocfg::AutoCfg::with_dir("target").unwrap();
|
||||||
|
|
||||||
|
// since ancient times...
|
||||||
|
ac.emit_has_path("std::vec::Vec");
|
||||||
|
ac.emit_path_cfg("std::vec::Vec", "has_vec");
|
||||||
|
|
||||||
|
// rustc 1.10.0
|
||||||
|
ac.emit_has_path("std::panic::PanicInfo");
|
||||||
|
ac.emit_path_cfg("std::panic::PanicInfo", "has_panic_info");
|
||||||
|
|
||||||
|
// rustc 1.20.0
|
||||||
|
ac.emit_has_path("std::mem::ManuallyDrop");
|
||||||
|
ac.emit_path_cfg("std::mem::ManuallyDrop", "has_manually_drop");
|
||||||
|
|
||||||
|
// rustc 1.25.0
|
||||||
|
ac.emit_has_path("std::ptr::NonNull");
|
||||||
|
ac.emit_path_cfg("std::ptr::NonNull", "has_non_null");
|
||||||
|
}
|
||||||
26
third_party/crates/autocfg-1.5.1/examples/traits.rs
vendored
Normal file
26
third_party/crates/autocfg-1.5.1/examples/traits.rs
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
extern crate autocfg;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Normally, cargo will set `OUT_DIR` for build scripts.
|
||||||
|
let ac = autocfg::AutoCfg::with_dir("target").unwrap();
|
||||||
|
|
||||||
|
// since ancient times...
|
||||||
|
ac.emit_has_trait("std::ops::Add");
|
||||||
|
ac.emit_trait_cfg("std::ops::Add", "has_ops");
|
||||||
|
|
||||||
|
// trait parameters have to be provided
|
||||||
|
ac.emit_has_trait("std::borrow::Borrow<str>");
|
||||||
|
ac.emit_trait_cfg("std::borrow::Borrow<str>", "has_borrow");
|
||||||
|
|
||||||
|
// rustc 1.8.0
|
||||||
|
ac.emit_has_trait("std::ops::AddAssign");
|
||||||
|
ac.emit_trait_cfg("std::ops::AddAssign", "has_assign_ops");
|
||||||
|
|
||||||
|
// rustc 1.12.0
|
||||||
|
ac.emit_has_trait("std::iter::Sum");
|
||||||
|
ac.emit_trait_cfg("std::iter::Sum", "has_sum");
|
||||||
|
|
||||||
|
// rustc 1.28.0
|
||||||
|
ac.emit_has_trait("std::alloc::GlobalAlloc");
|
||||||
|
ac.emit_trait_cfg("std::alloc::GlobalAlloc", "has_global_alloc");
|
||||||
|
}
|
||||||
9
third_party/crates/autocfg-1.5.1/examples/versions.rs
vendored
Normal file
9
third_party/crates/autocfg-1.5.1/examples/versions.rs
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
extern crate autocfg;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Normally, cargo will set `OUT_DIR` for build scripts.
|
||||||
|
let ac = autocfg::AutoCfg::with_dir("target").unwrap();
|
||||||
|
for i in 0..100 {
|
||||||
|
ac.emit_rustc_version(1, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
third_party/crates/autocfg-1.5.1/src/error.rs
vendored
Normal file
81
third_party/crates/autocfg-1.5.1/src/error.rs
vendored
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
|
use std::num;
|
||||||
|
use std::process;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
/// A common error type for the `autocfg` crate.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Error {
|
||||||
|
kind: ErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for Error {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"AutoCfg error"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&error::Error> {
|
||||||
|
match self.kind {
|
||||||
|
ErrorKind::Io(ref e) => Some(e),
|
||||||
|
ErrorKind::Num(ref e) => Some(e),
|
||||||
|
ErrorKind::Utf8(ref e) => Some(e),
|
||||||
|
ErrorKind::Process(_) | ErrorKind::Other(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
match self.kind {
|
||||||
|
ErrorKind::Io(ref e) => e.fmt(f),
|
||||||
|
ErrorKind::Num(ref e) => e.fmt(f),
|
||||||
|
ErrorKind::Utf8(ref e) => e.fmt(f),
|
||||||
|
ErrorKind::Process(ref status) => {
|
||||||
|
// Same message as the newer `ExitStatusError`
|
||||||
|
write!(f, "process exited unsuccessfully: {}", status)
|
||||||
|
}
|
||||||
|
ErrorKind::Other(s) => s.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ErrorKind {
|
||||||
|
Io(io::Error),
|
||||||
|
Num(num::ParseIntError),
|
||||||
|
Process(process::ExitStatus),
|
||||||
|
Utf8(str::Utf8Error),
|
||||||
|
Other(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_exit(status: process::ExitStatus) -> Error {
|
||||||
|
Error {
|
||||||
|
kind: ErrorKind::Process(status),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_io(e: io::Error) -> Error {
|
||||||
|
Error {
|
||||||
|
kind: ErrorKind::Io(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_num(e: num::ParseIntError) -> Error {
|
||||||
|
Error {
|
||||||
|
kind: ErrorKind::Num(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_utf8(e: str::Utf8Error) -> Error {
|
||||||
|
Error {
|
||||||
|
kind: ErrorKind::Utf8(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_str(s: &'static str) -> Error {
|
||||||
|
Error {
|
||||||
|
kind: ErrorKind::Other(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
636
third_party/crates/autocfg-1.5.1/src/lib.rs
vendored
Normal file
636
third_party/crates/autocfg-1.5.1/src/lib.rs
vendored
Normal file
@@ -0,0 +1,636 @@
|
|||||||
|
//! A Rust library for build scripts to automatically configure code based on
|
||||||
|
//! compiler support. Code snippets are dynamically tested to see if the `rustc`
|
||||||
|
//! will accept them, rather than hard-coding specific version support.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! ## Usage
|
||||||
|
//!
|
||||||
|
//! Add this to your `Cargo.toml`:
|
||||||
|
//!
|
||||||
|
//! ```toml
|
||||||
|
//! [build-dependencies]
|
||||||
|
//! autocfg = "1"
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Then use it in your `build.rs` script to detect compiler features. For
|
||||||
|
//! example, to test for 128-bit integer support, it might look like:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! extern crate autocfg;
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! # // Normally, cargo will set `OUT_DIR` for build scripts.
|
||||||
|
//! # let exe = std::env::current_exe().unwrap();
|
||||||
|
//! # std::env::set_var("OUT_DIR", exe.parent().unwrap());
|
||||||
|
//! let ac = autocfg::new();
|
||||||
|
//! ac.emit_has_type("i128");
|
||||||
|
//!
|
||||||
|
//! // (optional) We don't need to rerun for anything external.
|
||||||
|
//! autocfg::rerun_path("build.rs");
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! If the type test succeeds, this will write a `cargo:rustc-cfg=has_i128` line
|
||||||
|
//! for Cargo, which translates to Rust arguments `--cfg has_i128`. Then in the
|
||||||
|
//! rest of your Rust code, you can add `#[cfg(has_i128)]` conditions on code that
|
||||||
|
//! should only be used when the compiler supports it.
|
||||||
|
//!
|
||||||
|
//! ## Caution
|
||||||
|
//!
|
||||||
|
//! Many of the probing methods of `AutoCfg` document the particular template they
|
||||||
|
//! use, **subject to change**. The inputs are not validated to make sure they are
|
||||||
|
//! semantically correct for their expected use, so it's _possible_ to escape and
|
||||||
|
//! inject something unintended. However, such abuse is unsupported and will not
|
||||||
|
//! be considered when making changes to the templates.
|
||||||
|
|
||||||
|
#![deny(missing_debug_implementations)]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
// allow future warnings that can't be fixed while keeping 1.0 compatibility
|
||||||
|
#![allow(unknown_lints)]
|
||||||
|
#![allow(bare_trait_objects)]
|
||||||
|
#![allow(ellipsis_inclusive_range_patterns)]
|
||||||
|
|
||||||
|
/// Local macro to avoid `std::try!`, deprecated in Rust 1.39.
|
||||||
|
macro_rules! try {
|
||||||
|
($result:expr) => {
|
||||||
|
match $result {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(error) => return Err(error),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::fmt::Arguments;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::{stderr, Write};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Stdio;
|
||||||
|
#[allow(deprecated)]
|
||||||
|
use std::sync::atomic::ATOMIC_USIZE_INIT;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
|
mod rustc;
|
||||||
|
use rustc::Rustc;
|
||||||
|
|
||||||
|
mod version;
|
||||||
|
use version::Version;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
/// Helper to detect compiler features for `cfg` output in build scripts.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AutoCfg {
|
||||||
|
out_dir: PathBuf,
|
||||||
|
rustc: Rustc,
|
||||||
|
rustc_version: Version,
|
||||||
|
target: Option<OsString>,
|
||||||
|
no_std: bool,
|
||||||
|
edition: Option<String>,
|
||||||
|
rustflags: Vec<String>,
|
||||||
|
uuid: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a config flag for rustc on standard out.
|
||||||
|
///
|
||||||
|
/// This looks like: `cargo:rustc-cfg=CFG`
|
||||||
|
///
|
||||||
|
/// Cargo will use this in arguments to rustc, like `--cfg CFG`.
|
||||||
|
///
|
||||||
|
/// This does not automatically call [`emit_possibility`]
|
||||||
|
/// so the compiler my generate an [`unexpected_cfgs` warning][check-cfg-flags].
|
||||||
|
/// However, all the builtin emit methods on [`AutoCfg`] call [`emit_possibility`] automatically.
|
||||||
|
///
|
||||||
|
/// [check-cfg-flags]: https://blog.rust-lang.org/2024/05/06/check-cfg.html
|
||||||
|
pub fn emit(cfg: &str) {
|
||||||
|
println!("cargo:rustc-cfg={}", cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a line telling Cargo to rerun the build script if `path` changes.
|
||||||
|
///
|
||||||
|
/// This looks like: `cargo:rerun-if-changed=PATH`
|
||||||
|
///
|
||||||
|
/// This requires at least cargo 0.7.0, corresponding to rustc 1.6.0. Earlier
|
||||||
|
/// versions of cargo will simply ignore the directive.
|
||||||
|
pub fn rerun_path(path: &str) {
|
||||||
|
println!("cargo:rerun-if-changed={}", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a line telling Cargo to rerun the build script if the environment
|
||||||
|
/// variable `var` changes.
|
||||||
|
///
|
||||||
|
/// This looks like: `cargo:rerun-if-env-changed=VAR`
|
||||||
|
///
|
||||||
|
/// This requires at least cargo 0.21.0, corresponding to rustc 1.20.0. Earlier
|
||||||
|
/// versions of cargo will simply ignore the directive.
|
||||||
|
pub fn rerun_env(var: &str) {
|
||||||
|
println!("cargo:rerun-if-env-changed={}", var);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates to rustc that a config flag should not generate an [`unexpected_cfgs` warning][check-cfg-flags]
|
||||||
|
///
|
||||||
|
/// This looks like `cargo:rustc-check-cfg=cfg(VAR)`
|
||||||
|
///
|
||||||
|
/// As of rust 1.80, the compiler does [automatic checking of cfgs at compile time][check-cfg-flags].
|
||||||
|
/// All custom configuration flags must be known to rustc, or they will generate a warning.
|
||||||
|
/// This is done automatically when calling the builtin emit methods on [`AutoCfg`],
|
||||||
|
/// but not when calling [`autocfg::emit`](crate::emit) directly.
|
||||||
|
///
|
||||||
|
/// Versions before rust 1.80 will simply ignore this directive.
|
||||||
|
///
|
||||||
|
/// This function indicates to the compiler that the config flag never has a value.
|
||||||
|
/// If this is not desired, see [the blog post][check-cfg].
|
||||||
|
///
|
||||||
|
/// [check-cfg-flags]: https://blog.rust-lang.org/2024/05/06/check-cfg.html
|
||||||
|
pub fn emit_possibility(cfg: &str) {
|
||||||
|
println!("cargo:rustc-check-cfg=cfg({})", cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `AutoCfg` instance.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if `AutoCfg::new()` returns an error.
|
||||||
|
pub fn new() -> AutoCfg {
|
||||||
|
AutoCfg::new().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AutoCfg {
|
||||||
|
/// Creates a new `AutoCfg` instance.
|
||||||
|
///
|
||||||
|
/// # Common errors
|
||||||
|
///
|
||||||
|
/// - `rustc` can't be executed, from `RUSTC` or in the `PATH`.
|
||||||
|
/// - The version output from `rustc` can't be parsed.
|
||||||
|
/// - `OUT_DIR` is not set in the environment, or is not a writable directory.
|
||||||
|
///
|
||||||
|
pub fn new() -> Result<Self, Error> {
|
||||||
|
match env::var_os("OUT_DIR") {
|
||||||
|
Some(d) => Self::with_dir(d),
|
||||||
|
None => Err(error::from_str("no OUT_DIR specified!")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `AutoCfg` instance with the specified output directory.
|
||||||
|
///
|
||||||
|
/// # Common errors
|
||||||
|
///
|
||||||
|
/// - `rustc` can't be executed, from `RUSTC` or in the `PATH`.
|
||||||
|
/// - The version output from `rustc` can't be parsed.
|
||||||
|
/// - `dir` is not a writable directory.
|
||||||
|
///
|
||||||
|
pub fn with_dir<T: Into<PathBuf>>(dir: T) -> Result<Self, Error> {
|
||||||
|
let rustc = Rustc::new();
|
||||||
|
let rustc_version = try!(rustc.version());
|
||||||
|
|
||||||
|
let target = env::var_os("TARGET");
|
||||||
|
|
||||||
|
// Sanity check the output directory
|
||||||
|
let dir = dir.into();
|
||||||
|
let meta = try!(fs::metadata(&dir).map_err(error::from_io));
|
||||||
|
if !meta.is_dir() || meta.permissions().readonly() {
|
||||||
|
return Err(error::from_str("output path is not a writable directory"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ac = AutoCfg {
|
||||||
|
rustflags: rustflags(&target, &dir),
|
||||||
|
out_dir: dir,
|
||||||
|
rustc: rustc,
|
||||||
|
rustc_version: rustc_version,
|
||||||
|
target: target,
|
||||||
|
no_std: false,
|
||||||
|
edition: None,
|
||||||
|
uuid: new_uuid(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sanity check with and without `std`.
|
||||||
|
if ac.probe_raw("").is_err() {
|
||||||
|
if ac.probe_raw("#![no_std]").is_ok() {
|
||||||
|
ac.no_std = true;
|
||||||
|
} else {
|
||||||
|
// Neither worked, so assume nothing...
|
||||||
|
let warning = b"warning: autocfg could not probe for `std`\n";
|
||||||
|
stderr().write_all(warning).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ac)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether `AutoCfg` is using `#![no_std]` in its probes.
|
||||||
|
///
|
||||||
|
/// This is automatically detected during construction -- if an empty probe
|
||||||
|
/// fails while one with `#![no_std]` succeeds, then the attribute will be
|
||||||
|
/// used for all further probes. This is usually only necessary when the
|
||||||
|
/// `TARGET` lacks `std` altogether. If neither succeeds, `no_std` is not
|
||||||
|
/// set, but that `AutoCfg` will probably only work for version checks.
|
||||||
|
///
|
||||||
|
/// This attribute changes the implicit [prelude] from `std` to `core`,
|
||||||
|
/// which may affect the paths you need to use in other probes. It also
|
||||||
|
/// restricts some types that otherwise get additional methods in `std`,
|
||||||
|
/// like floating-point trigonometry and slice sorting.
|
||||||
|
///
|
||||||
|
/// See also [`set_no_std`](#method.set_no_std).
|
||||||
|
///
|
||||||
|
/// [prelude]: https://doc.rust-lang.org/reference/names/preludes.html#the-no_std-attribute
|
||||||
|
pub fn no_std(&self) -> bool {
|
||||||
|
self.no_std
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether `AutoCfg` should use `#![no_std]` in its probes.
|
||||||
|
///
|
||||||
|
/// See also [`no_std`](#method.no_std).
|
||||||
|
pub fn set_no_std(&mut self, no_std: bool) {
|
||||||
|
self.no_std = no_std;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `--edition` string that is currently being passed to `rustc`, if any,
|
||||||
|
/// as configured by the [`set_edition`][Self::set_edition] method.
|
||||||
|
pub fn edition(&self) -> Option<&str> {
|
||||||
|
match self.edition {
|
||||||
|
Some(ref edition) => Some(&**edition),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `--edition` string that will be passed to `rustc`,
|
||||||
|
/// or `None` to leave the compiler at its default edition.
|
||||||
|
///
|
||||||
|
/// See also [The Rust Edition Guide](https://doc.rust-lang.org/edition-guide/).
|
||||||
|
///
|
||||||
|
/// **Warning:** Setting an unsupported edition will likely cause **all** subsequent probes to
|
||||||
|
/// fail! As of this writing, the known editions and their minimum Rust versions are:
|
||||||
|
///
|
||||||
|
/// | Edition | Version |
|
||||||
|
/// | ------- | ---------- |
|
||||||
|
/// | 2015 | 1.27.0[^1] |
|
||||||
|
/// | 2018 | 1.31.0 |
|
||||||
|
/// | 2021 | 1.56.0 |
|
||||||
|
/// | 2024 | 1.85.0 |
|
||||||
|
///
|
||||||
|
/// [^1]: Prior to 1.27.0, Rust was effectively 2015 Edition by default, but the concept hadn't
|
||||||
|
/// been established yet, so the explicit `--edition` flag wasn't supported either.
|
||||||
|
pub fn set_edition(&mut self, edition: Option<String>) {
|
||||||
|
self.edition = edition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether the current `rustc` reports a version greater than
|
||||||
|
/// or equal to "`major`.`minor`".
|
||||||
|
pub fn probe_rustc_version(&self, major: usize, minor: usize) -> bool {
|
||||||
|
self.rustc_version >= Version::new(major, minor, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a `cfg` value of the form `rustc_major_minor`, like `rustc_1_29`,
|
||||||
|
/// if the current `rustc` is at least that version.
|
||||||
|
pub fn emit_rustc_version(&self, major: usize, minor: usize) {
|
||||||
|
let cfg_flag = format!("rustc_{}_{}", major, minor);
|
||||||
|
emit_possibility(&cfg_flag);
|
||||||
|
if self.probe_rustc_version(major, minor) {
|
||||||
|
emit(&cfg_flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new (hopefully unique) crate name for probes.
|
||||||
|
fn new_crate_name(&self) -> String {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
static ID: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||||
|
|
||||||
|
let id = ID.fetch_add(1, Ordering::Relaxed);
|
||||||
|
format!("autocfg_{:016x}_{}", self.uuid, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn probe_fmt<'a>(&self, source: Arguments<'a>) -> Result<(), Error> {
|
||||||
|
let crate_name = self.new_crate_name();
|
||||||
|
let mut command = self.rustc.command();
|
||||||
|
command
|
||||||
|
.arg("--crate-name")
|
||||||
|
.arg(&crate_name)
|
||||||
|
.arg("--crate-type=lib")
|
||||||
|
.arg("--out-dir")
|
||||||
|
.arg(&self.out_dir)
|
||||||
|
.arg("--emit=llvm-ir");
|
||||||
|
|
||||||
|
if let Some(edition) = self.edition.as_ref() {
|
||||||
|
command.arg("--edition").arg(edition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(target) = self.target.as_ref() {
|
||||||
|
command.arg("--target").arg(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
command.args(&self.rustflags);
|
||||||
|
|
||||||
|
command.arg("-").stdin(Stdio::piped());
|
||||||
|
let mut child = try!(command.spawn().map_err(error::from_io));
|
||||||
|
let mut stdin = child.stdin.take().expect("rustc stdin");
|
||||||
|
|
||||||
|
try!(stdin.write_fmt(source).map_err(error::from_io));
|
||||||
|
drop(stdin);
|
||||||
|
|
||||||
|
match child.wait() {
|
||||||
|
Ok(status) if status.success() => {
|
||||||
|
// Try to remove the output file so it doesn't look like a build product for
|
||||||
|
// systems like bazel -- but this is best-effort, so we can ignore failure.
|
||||||
|
// The probe itself is already considered successful at this point.
|
||||||
|
let mut file = self.out_dir.join(crate_name);
|
||||||
|
file.set_extension("ll");
|
||||||
|
let _ = fs::remove_file(file);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(status) => Err(error::from_exit(status)),
|
||||||
|
Err(error) => Err(error::from_io(error)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn probe<'a>(&self, code: Arguments<'a>) -> bool {
|
||||||
|
let result = if self.no_std {
|
||||||
|
self.probe_fmt(format_args!("#![no_std]\n{}", code))
|
||||||
|
} else {
|
||||||
|
self.probe_fmt(code)
|
||||||
|
};
|
||||||
|
result.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether the given code can be compiled as a Rust library.
|
||||||
|
///
|
||||||
|
/// This will only return `Ok` if the compiler ran and exited successfully,
|
||||||
|
/// per `ExitStatus::success()`.
|
||||||
|
/// The code is passed to the compiler exactly as-is, notably not even
|
||||||
|
/// adding the [`#![no_std]`][Self::no_std] attribute like other probes.
|
||||||
|
///
|
||||||
|
/// Raw probes are useful for testing functionality that's not yet covered
|
||||||
|
/// by the rest of the `AutoCfg` API. For example, the following attribute
|
||||||
|
/// **must** be used at the crate level, so it wouldn't work within the code
|
||||||
|
/// templates used by other `probe_*` methods.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # extern crate autocfg;
|
||||||
|
/// # // Normally, cargo will set `OUT_DIR` for build scripts.
|
||||||
|
/// # let exe = std::env::current_exe().unwrap();
|
||||||
|
/// # std::env::set_var("OUT_DIR", exe.parent().unwrap());
|
||||||
|
/// let ac = autocfg::new();
|
||||||
|
/// assert!(ac.probe_raw("#![no_builtins]").is_ok());
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Rust nightly features could be tested as well -- ideally including a
|
||||||
|
/// code sample to ensure the unstable feature still works as expected.
|
||||||
|
/// For example, `slice::group_by` was renamed to `chunk_by` when it was
|
||||||
|
/// stabilized, even though the feature name was unchanged, so testing the
|
||||||
|
/// `#![feature(..)]` alone wouldn't reveal that. For larger snippets,
|
||||||
|
/// [`include_str!`] may be useful to load them from separate files.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # extern crate autocfg;
|
||||||
|
/// # // Normally, cargo will set `OUT_DIR` for build scripts.
|
||||||
|
/// # let exe = std::env::current_exe().unwrap();
|
||||||
|
/// # std::env::set_var("OUT_DIR", exe.parent().unwrap());
|
||||||
|
/// let ac = autocfg::new();
|
||||||
|
/// let code = r#"
|
||||||
|
/// #![feature(slice_group_by)]
|
||||||
|
/// pub fn probe(slice: &[i32]) -> impl Iterator<Item = &[i32]> {
|
||||||
|
/// slice.group_by(|a, b| a == b)
|
||||||
|
/// }
|
||||||
|
/// "#;
|
||||||
|
/// if ac.probe_raw(code).is_ok() {
|
||||||
|
/// autocfg::emit("has_slice_group_by");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn probe_raw(&self, code: &str) -> Result<(), Error> {
|
||||||
|
self.probe_fmt(format_args!("{}", code))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether the given sysroot crate can be used.
|
||||||
|
///
|
||||||
|
/// The test code is subject to change, but currently looks like:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// extern crate CRATE as probe;
|
||||||
|
/// ```
|
||||||
|
pub fn probe_sysroot_crate(&self, name: &str) -> bool {
|
||||||
|
// Note: `as _` wasn't stabilized until Rust 1.33
|
||||||
|
self.probe(format_args!("extern crate {} as probe;", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits a config value `has_CRATE` if `probe_sysroot_crate` returns true.
|
||||||
|
pub fn emit_sysroot_crate(&self, name: &str) {
|
||||||
|
let cfg_flag = format!("has_{}", mangle(name));
|
||||||
|
emit_possibility(&cfg_flag);
|
||||||
|
if self.probe_sysroot_crate(name) {
|
||||||
|
emit(&cfg_flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether the given path can be used.
|
||||||
|
///
|
||||||
|
/// The test code is subject to change, but currently looks like:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// pub use PATH;
|
||||||
|
/// ```
|
||||||
|
pub fn probe_path(&self, path: &str) -> bool {
|
||||||
|
self.probe(format_args!("pub use {};", path))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits a config value `has_PATH` if `probe_path` returns true.
|
||||||
|
///
|
||||||
|
/// Any non-identifier characters in the `path` will be replaced with
|
||||||
|
/// `_` in the generated config value.
|
||||||
|
pub fn emit_has_path(&self, path: &str) {
|
||||||
|
self.emit_path_cfg(path, &format!("has_{}", mangle(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits the given `cfg` value if `probe_path` returns true.
|
||||||
|
pub fn emit_path_cfg(&self, path: &str, cfg: &str) {
|
||||||
|
emit_possibility(cfg);
|
||||||
|
if self.probe_path(path) {
|
||||||
|
emit(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether the given trait can be used.
|
||||||
|
///
|
||||||
|
/// The test code is subject to change, but currently looks like:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// pub trait Probe: TRAIT + Sized {}
|
||||||
|
/// ```
|
||||||
|
pub fn probe_trait(&self, name: &str) -> bool {
|
||||||
|
self.probe(format_args!("pub trait Probe: {} + Sized {{}}", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits a config value `has_TRAIT` if `probe_trait` returns true.
|
||||||
|
///
|
||||||
|
/// Any non-identifier characters in the trait `name` will be replaced with
|
||||||
|
/// `_` in the generated config value.
|
||||||
|
pub fn emit_has_trait(&self, name: &str) {
|
||||||
|
self.emit_trait_cfg(name, &format!("has_{}", mangle(name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits the given `cfg` value if `probe_trait` returns true.
|
||||||
|
pub fn emit_trait_cfg(&self, name: &str, cfg: &str) {
|
||||||
|
emit_possibility(cfg);
|
||||||
|
if self.probe_trait(name) {
|
||||||
|
emit(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether the given type can be used.
|
||||||
|
///
|
||||||
|
/// The test code is subject to change, but currently looks like:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// pub type Probe = TYPE;
|
||||||
|
/// ```
|
||||||
|
pub fn probe_type(&self, name: &str) -> bool {
|
||||||
|
self.probe(format_args!("pub type Probe = {};", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits a config value `has_TYPE` if `probe_type` returns true.
|
||||||
|
///
|
||||||
|
/// Any non-identifier characters in the type `name` will be replaced with
|
||||||
|
/// `_` in the generated config value.
|
||||||
|
pub fn emit_has_type(&self, name: &str) {
|
||||||
|
self.emit_type_cfg(name, &format!("has_{}", mangle(name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits the given `cfg` value if `probe_type` returns true.
|
||||||
|
pub fn emit_type_cfg(&self, name: &str, cfg: &str) {
|
||||||
|
emit_possibility(cfg);
|
||||||
|
if self.probe_type(name) {
|
||||||
|
emit(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether the given expression can be used.
|
||||||
|
///
|
||||||
|
/// The test code is subject to change, but currently looks like:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// pub fn probe() { let _ = EXPR; }
|
||||||
|
/// ```
|
||||||
|
pub fn probe_expression(&self, expr: &str) -> bool {
|
||||||
|
self.probe(format_args!("pub fn probe() {{ let _ = {}; }}", expr))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits the given `cfg` value if `probe_expression` returns true.
|
||||||
|
pub fn emit_expression_cfg(&self, expr: &str, cfg: &str) {
|
||||||
|
emit_possibility(cfg);
|
||||||
|
if self.probe_expression(expr) {
|
||||||
|
emit(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests whether the given constant expression can be used.
|
||||||
|
///
|
||||||
|
/// The test code is subject to change, but currently looks like:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// pub const PROBE: () = ((), EXPR).0;
|
||||||
|
/// ```
|
||||||
|
pub fn probe_constant(&self, expr: &str) -> bool {
|
||||||
|
self.probe(format_args!("pub const PROBE: () = ((), {}).0;", expr))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits the given `cfg` value if `probe_constant` returns true.
|
||||||
|
pub fn emit_constant_cfg(&self, expr: &str, cfg: &str) {
|
||||||
|
emit_possibility(cfg);
|
||||||
|
if self.probe_constant(expr) {
|
||||||
|
emit(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mangle(s: &str) -> String {
|
||||||
|
s.chars()
|
||||||
|
.map(|c| match c {
|
||||||
|
'A'...'Z' | 'a'...'z' | '0'...'9' => c,
|
||||||
|
_ => '_',
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dir_contains_target(
|
||||||
|
target: &Option<OsString>,
|
||||||
|
dir: &Path,
|
||||||
|
cargo_target_dir: Option<OsString>,
|
||||||
|
) -> bool {
|
||||||
|
target
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|target| {
|
||||||
|
dir.to_str().and_then(|dir| {
|
||||||
|
let mut cargo_target_dir = cargo_target_dir
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|| PathBuf::from("target"));
|
||||||
|
cargo_target_dir.push(target);
|
||||||
|
|
||||||
|
cargo_target_dir
|
||||||
|
.to_str()
|
||||||
|
.map(|cargo_target_dir| dir.contains(cargo_target_dir))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rustflags(target: &Option<OsString>, dir: &Path) -> Vec<String> {
|
||||||
|
// Starting with rust-lang/cargo#9601, shipped in Rust 1.55, Cargo always sets
|
||||||
|
// CARGO_ENCODED_RUSTFLAGS for any host/target build script invocation. This
|
||||||
|
// includes any source of flags, whether from the environment, toml config, or
|
||||||
|
// whatever may come in the future. The value is either an empty string, or a
|
||||||
|
// list of arguments separated by the ASCII unit separator (US), 0x1f.
|
||||||
|
if let Ok(a) = env::var("CARGO_ENCODED_RUSTFLAGS") {
|
||||||
|
return if a.is_empty() {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
a.split('\x1f').map(str::to_string).collect()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we have to take a more heuristic approach, and we don't
|
||||||
|
// support values from toml config at all.
|
||||||
|
//
|
||||||
|
// Cargo only applies RUSTFLAGS for building TARGET artifact in
|
||||||
|
// cross-compilation environment. Sadly, we don't have a way to detect
|
||||||
|
// when we're building HOST artifact in a cross-compilation environment,
|
||||||
|
// so for now we only apply RUSTFLAGS when cross-compiling an artifact.
|
||||||
|
//
|
||||||
|
// See https://github.com/cuviper/autocfg/pull/10#issuecomment-527575030.
|
||||||
|
if *target != env::var_os("HOST")
|
||||||
|
|| dir_contains_target(target, dir, env::var_os("CARGO_TARGET_DIR"))
|
||||||
|
{
|
||||||
|
if let Ok(rustflags) = env::var("RUSTFLAGS") {
|
||||||
|
// This is meant to match how cargo handles the RUSTFLAGS environment variable.
|
||||||
|
// See https://github.com/rust-lang/cargo/blob/69aea5b6f69add7c51cca939a79644080c0b0ba0/src/cargo/core/compiler/build_context/target_info.rs#L434-L441
|
||||||
|
return rustflags
|
||||||
|
.split(' ')
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a numeric ID to use in probe crate names.
|
||||||
|
///
|
||||||
|
/// This attempts to be random, within the constraints of Rust 1.0 and no dependencies.
|
||||||
|
fn new_uuid() -> u64 {
|
||||||
|
const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
|
||||||
|
const FNV_PRIME: u64 = 0x100_0000_01b3;
|
||||||
|
|
||||||
|
// This set should have an actual random hasher.
|
||||||
|
let set: std::collections::HashSet<u64> = (0..256).collect();
|
||||||
|
|
||||||
|
// Feed the `HashSet`-shuffled order into FNV-1a.
|
||||||
|
let mut hash: u64 = FNV_OFFSET_BASIS;
|
||||||
|
for x in set {
|
||||||
|
hash = (hash ^ x).wrapping_mul(FNV_PRIME);
|
||||||
|
}
|
||||||
|
hash
|
||||||
|
}
|
||||||
89
third_party/crates/autocfg-1.5.1/src/rustc.rs
vendored
Normal file
89
third_party/crates/autocfg-1.5.1/src/rustc.rs
vendored
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use super::error::Error;
|
||||||
|
use super::version::Version;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Rustc {
|
||||||
|
rustc: PathBuf,
|
||||||
|
rustc_wrapper: Option<PathBuf>,
|
||||||
|
rustc_workspace_wrapper: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rustc {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Rustc {
|
||||||
|
rustc: env::var_os("RUSTC")
|
||||||
|
.unwrap_or_else(|| "rustc".into())
|
||||||
|
.into(),
|
||||||
|
rustc_wrapper: get_rustc_wrapper(false),
|
||||||
|
rustc_workspace_wrapper: get_rustc_wrapper(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the command with possible wrappers.
|
||||||
|
pub fn command(&self) -> Command {
|
||||||
|
let mut rustc = self
|
||||||
|
.rustc_wrapper
|
||||||
|
.iter()
|
||||||
|
.chain(self.rustc_workspace_wrapper.iter())
|
||||||
|
.chain(Some(&self.rustc));
|
||||||
|
let mut command = Command::new(rustc.next().unwrap());
|
||||||
|
for arg in rustc {
|
||||||
|
command.arg(arg);
|
||||||
|
}
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to get the `rustc` version.
|
||||||
|
pub fn version(&self) -> Result<Version, Error> {
|
||||||
|
// Some wrappers like clippy-driver don't pass through version commands,
|
||||||
|
// so we try to fall back to combinations without each wrapper.
|
||||||
|
macro_rules! try_version {
|
||||||
|
($command:expr) => {
|
||||||
|
if let Ok(value) = Version::from_command($command) {
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let rustc = &self.rustc;
|
||||||
|
if let Some(ref rw) = self.rustc_wrapper {
|
||||||
|
if let Some(ref rww) = self.rustc_workspace_wrapper {
|
||||||
|
try_version!(Command::new(rw).args(&[rww, rustc]));
|
||||||
|
}
|
||||||
|
try_version!(Command::new(rw).arg(rustc));
|
||||||
|
}
|
||||||
|
if let Some(ref rww) = self.rustc_workspace_wrapper {
|
||||||
|
try_version!(Command::new(rww).arg(rustc));
|
||||||
|
}
|
||||||
|
Version::from_command(&mut Command::new(rustc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_rustc_wrapper(workspace: bool) -> Option<PathBuf> {
|
||||||
|
// We didn't really know whether the workspace wrapper is applicable until Cargo started
|
||||||
|
// deliberately setting or unsetting it in rust-lang/cargo#9601. We'll use the encoded
|
||||||
|
// rustflags as a proxy for that change for now, but we could instead check version 1.55.
|
||||||
|
if workspace && env::var_os("CARGO_ENCODED_RUSTFLAGS").is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = if workspace {
|
||||||
|
"RUSTC_WORKSPACE_WRAPPER"
|
||||||
|
} else {
|
||||||
|
"RUSTC_WRAPPER"
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(wrapper) = env::var_os(name) {
|
||||||
|
// NB: `OsStr` didn't get `len` or `is_empty` until 1.9.
|
||||||
|
if wrapper != OsString::new() {
|
||||||
|
return Some(wrapper.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
54
third_party/crates/autocfg-1.5.1/src/tests.rs
vendored
Normal file
54
third_party/crates/autocfg-1.5.1/src/tests.rs
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn version_cmp() {
|
||||||
|
use super::version::Version;
|
||||||
|
let v123 = Version::new(1, 2, 3);
|
||||||
|
|
||||||
|
assert!(Version::new(1, 0, 0) < v123);
|
||||||
|
assert!(Version::new(1, 2, 2) < v123);
|
||||||
|
assert!(Version::new(1, 2, 3) == v123);
|
||||||
|
assert!(Version::new(1, 2, 4) > v123);
|
||||||
|
assert!(Version::new(1, 10, 0) > v123);
|
||||||
|
assert!(Version::new(2, 0, 0) > v123);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dir_does_not_contain_target() {
|
||||||
|
assert!(!super::dir_contains_target(
|
||||||
|
&Some("x86_64-unknown-linux-gnu".into()),
|
||||||
|
Path::new("/project/target/debug/build/project-ea75983148559682/out"),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dir_does_contain_target() {
|
||||||
|
assert!(super::dir_contains_target(
|
||||||
|
&Some("x86_64-unknown-linux-gnu".into()),
|
||||||
|
Path::new(
|
||||||
|
"/project/target/x86_64-unknown-linux-gnu/debug/build/project-0147aca016480b9d/out"
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dir_does_not_contain_target_with_custom_target_dir() {
|
||||||
|
assert!(!super::dir_contains_target(
|
||||||
|
&Some("x86_64-unknown-linux-gnu".into()),
|
||||||
|
Path::new("/project/custom/debug/build/project-ea75983148559682/out"),
|
||||||
|
Some("custom".into()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dir_does_contain_target_with_custom_target_dir() {
|
||||||
|
assert!(super::dir_contains_target(
|
||||||
|
&Some("x86_64-unknown-linux-gnu".into()),
|
||||||
|
Path::new(
|
||||||
|
"/project/custom/x86_64-unknown-linux-gnu/debug/build/project-0147aca016480b9d/out"
|
||||||
|
),
|
||||||
|
Some("custom".into()),
|
||||||
|
));
|
||||||
|
}
|
||||||
65
third_party/crates/autocfg-1.5.1/src/version.rs
vendored
Normal file
65
third_party/crates/autocfg-1.5.1/src/version.rs
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
use std::process::Command;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use super::{error, Error};
|
||||||
|
|
||||||
|
/// A version structure for making relative comparisons.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Version {
|
||||||
|
major: usize,
|
||||||
|
minor: usize,
|
||||||
|
patch: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Version {
|
||||||
|
/// Creates a `Version` instance for a specific `major.minor.patch` version.
|
||||||
|
pub fn new(major: usize, minor: usize, patch: usize) -> Self {
|
||||||
|
Version {
|
||||||
|
major: major,
|
||||||
|
minor: minor,
|
||||||
|
patch: patch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_command(command: &mut Command) -> Result<Self, Error> {
|
||||||
|
// Get rustc's verbose version
|
||||||
|
let output = try!(command
|
||||||
|
.args(&["--version", "--verbose"])
|
||||||
|
.output()
|
||||||
|
.map_err(error::from_io));
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(error::from_str("could not execute rustc"));
|
||||||
|
}
|
||||||
|
let output = try!(str::from_utf8(&output.stdout).map_err(error::from_utf8));
|
||||||
|
|
||||||
|
// Find the release line in the verbose version output.
|
||||||
|
let release = match output.lines().find(|line| line.starts_with("release: ")) {
|
||||||
|
Some(line) => &line["release: ".len()..],
|
||||||
|
None => return Err(error::from_str("could not find rustc release")),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Strip off any extra channel info, e.g. "-beta.N", "-nightly"
|
||||||
|
let version = match release.find('-') {
|
||||||
|
Some(i) => &release[..i],
|
||||||
|
None => release,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Split the version into semver components.
|
||||||
|
let mut iter = version.splitn(3, '.');
|
||||||
|
let major = try!(iter
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| error::from_str("missing major version")));
|
||||||
|
let minor = try!(iter
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| error::from_str("missing minor version")));
|
||||||
|
let patch = try!(iter
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| error::from_str("missing patch version")));
|
||||||
|
|
||||||
|
Ok(Version::new(
|
||||||
|
try!(major.parse().map_err(error::from_num)),
|
||||||
|
try!(minor.parse().map_err(error::from_num)),
|
||||||
|
try!(patch.parse().map_err(error::from_num)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
28
third_party/crates/autocfg-1.5.1/tests/no_std.rs
vendored
Normal file
28
third_party/crates/autocfg-1.5.1/tests/no_std.rs
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
extern crate autocfg;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
mod support;
|
||||||
|
|
||||||
|
/// Tests that we can control the use of `#![no_std]`.
|
||||||
|
#[test]
|
||||||
|
fn no_std() {
|
||||||
|
// Clear the CI `TARGET`, if any, so we're just dealing with the
|
||||||
|
// host target which always has `std` available.
|
||||||
|
env::remove_var("TARGET");
|
||||||
|
|
||||||
|
// Use the same path as this test binary.
|
||||||
|
let out = support::out_dir();
|
||||||
|
|
||||||
|
let mut ac = autocfg::AutoCfg::with_dir(out.as_ref()).unwrap();
|
||||||
|
assert!(!ac.no_std());
|
||||||
|
assert!(ac.probe_path("std::mem"));
|
||||||
|
|
||||||
|
// `#![no_std]` was stabilized in Rust 1.6
|
||||||
|
if ac.probe_rustc_version(1, 6) {
|
||||||
|
ac.set_no_std(true);
|
||||||
|
assert!(ac.no_std());
|
||||||
|
assert!(!ac.probe_path("std::mem"));
|
||||||
|
assert!(ac.probe_path("core::mem"));
|
||||||
|
}
|
||||||
|
}
|
||||||
34
third_party/crates/autocfg-1.5.1/tests/rustflags.rs
vendored
Normal file
34
third_party/crates/autocfg-1.5.1/tests/rustflags.rs
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
extern crate autocfg;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
mod support;
|
||||||
|
|
||||||
|
/// Tests that autocfg uses the RUSTFLAGS or CARGO_ENCODED_RUSTFLAGS
|
||||||
|
/// environment variables when running rustc.
|
||||||
|
#[test]
|
||||||
|
fn test_with_sysroot() {
|
||||||
|
let dir = support::exe_dir();
|
||||||
|
let out = support::out_dir();
|
||||||
|
|
||||||
|
// If we have encoded rustflags, they take precedence, even if empty.
|
||||||
|
env::set_var("CARGO_ENCODED_RUSTFLAGS", "");
|
||||||
|
env::set_var("RUSTFLAGS", &format!("-L {}", dir.display()));
|
||||||
|
let ac = autocfg::AutoCfg::with_dir(out.as_ref()).unwrap();
|
||||||
|
assert!(ac.probe_sysroot_crate("std"));
|
||||||
|
assert!(!ac.probe_sysroot_crate("autocfg"));
|
||||||
|
|
||||||
|
// Now try again with useful encoded args.
|
||||||
|
env::set_var(
|
||||||
|
"CARGO_ENCODED_RUSTFLAGS",
|
||||||
|
&format!("-L\x1f{}", dir.display()),
|
||||||
|
);
|
||||||
|
let ac = autocfg::AutoCfg::with_dir(out.as_ref()).unwrap();
|
||||||
|
assert!(ac.probe_sysroot_crate("autocfg"));
|
||||||
|
|
||||||
|
// Try the old-style RUSTFLAGS, ensuring HOST != TARGET.
|
||||||
|
env::remove_var("CARGO_ENCODED_RUSTFLAGS");
|
||||||
|
env::set_var("HOST", "lol");
|
||||||
|
let ac = autocfg::AutoCfg::with_dir(out.as_ref()).unwrap();
|
||||||
|
assert!(ac.probe_sysroot_crate("autocfg"));
|
||||||
|
}
|
||||||
21
third_party/crates/autocfg-1.5.1/tests/support/mod.rs
vendored
Normal file
21
third_party/crates/autocfg-1.5.1/tests/support/mod.rs
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::env;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// The directory containing this test binary.
|
||||||
|
pub fn exe_dir() -> PathBuf {
|
||||||
|
let exe = env::current_exe().unwrap();
|
||||||
|
exe.parent().unwrap().to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The directory to use for test probes.
|
||||||
|
pub fn out_dir() -> Cow<'static, Path> {
|
||||||
|
if let Some(tmpdir) = option_env!("CARGO_TARGET_TMPDIR") {
|
||||||
|
Cow::Borrowed(tmpdir.as_ref())
|
||||||
|
} else if let Some(tmpdir) = env::var_os("TESTS_TARGET_DIR") {
|
||||||
|
Cow::Owned(tmpdir.into())
|
||||||
|
} else {
|
||||||
|
// Use the same path as this test binary.
|
||||||
|
Cow::Owned(exe_dir())
|
||||||
|
}
|
||||||
|
}
|
||||||
189
third_party/crates/autocfg-1.5.1/tests/tests.rs
vendored
Normal file
189
third_party/crates/autocfg-1.5.1/tests/tests.rs
vendored
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
extern crate autocfg;
|
||||||
|
|
||||||
|
use autocfg::AutoCfg;
|
||||||
|
|
||||||
|
mod support;
|
||||||
|
|
||||||
|
fn core_std(ac: &AutoCfg, path: &str) -> String {
|
||||||
|
let krate = if ac.no_std() { "core" } else { "std" };
|
||||||
|
format!("{}::{}", krate, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_std(ac: &AutoCfg, probe_result: bool) {
|
||||||
|
assert_eq!(!ac.no_std(), probe_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_min(ac: &AutoCfg, major: usize, minor: usize, probe_result: bool) {
|
||||||
|
assert_eq!(ac.probe_rustc_version(major, minor), probe_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autocfg_for_test() -> AutoCfg {
|
||||||
|
AutoCfg::with_dir(support::out_dir().as_ref()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn autocfg_version() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
assert!(ac.probe_rustc_version(1, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_add() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
let add = core_std(&ac, "ops::Add");
|
||||||
|
let add_rhs = add.clone() + "<i32>";
|
||||||
|
let add_rhs_output = add.clone() + "<i32, Output = i32>";
|
||||||
|
let dyn_add_rhs_output = "dyn ".to_string() + &*add_rhs_output;
|
||||||
|
assert!(ac.probe_path(&add));
|
||||||
|
assert!(ac.probe_trait(&add));
|
||||||
|
assert!(ac.probe_trait(&add_rhs));
|
||||||
|
assert!(ac.probe_trait(&add_rhs_output));
|
||||||
|
assert_min(&ac, 1, 27, ac.probe_type(&dyn_add_rhs_output));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_as_ref() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
let as_ref = core_std(&ac, "convert::AsRef");
|
||||||
|
let as_ref_str = as_ref.clone() + "<str>";
|
||||||
|
let dyn_as_ref_str = "dyn ".to_string() + &*as_ref_str;
|
||||||
|
assert!(ac.probe_path(&as_ref));
|
||||||
|
assert!(ac.probe_trait(&as_ref_str));
|
||||||
|
assert!(ac.probe_type(&as_ref_str));
|
||||||
|
assert_min(&ac, 1, 27, ac.probe_type(&dyn_as_ref_str));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_i128() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
let i128_path = core_std(&ac, "i128");
|
||||||
|
assert_min(&ac, 1, 26, ac.probe_path(&i128_path));
|
||||||
|
assert_min(&ac, 1, 26, ac.probe_type("i128"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_sum() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
let sum = core_std(&ac, "iter::Sum");
|
||||||
|
let sum_i32 = sum.clone() + "<i32>";
|
||||||
|
let dyn_sum_i32 = "dyn ".to_string() + &*sum_i32;
|
||||||
|
assert_min(&ac, 1, 12, ac.probe_path(&sum));
|
||||||
|
assert_min(&ac, 1, 12, ac.probe_trait(&sum));
|
||||||
|
assert_min(&ac, 1, 12, ac.probe_trait(&sum_i32));
|
||||||
|
assert_min(&ac, 1, 12, ac.probe_type(&sum_i32));
|
||||||
|
assert_min(&ac, 1, 27, ac.probe_type(&dyn_sum_i32));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_std() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
assert_std(&ac, ac.probe_sysroot_crate("std"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_alloc() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
assert_min(&ac, 1, 36, ac.probe_sysroot_crate("alloc"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_bad_sysroot_crate() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
assert!(!ac.probe_sysroot_crate("doesnt_exist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_no_std() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
assert!(ac.probe_type("i32"));
|
||||||
|
assert!(ac.probe_type("[i32]"));
|
||||||
|
assert_std(&ac, ac.probe_type("Vec<i32>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_expression() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
assert!(ac.probe_expression(r#""test".trim_left()"#));
|
||||||
|
assert_min(&ac, 1, 30, ac.probe_expression(r#""test".trim_start()"#));
|
||||||
|
assert_std(&ac, ac.probe_expression("[1, 2, 3].to_vec()"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_constant() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
assert!(ac.probe_constant("1 + 2 + 3"));
|
||||||
|
assert_min(
|
||||||
|
&ac,
|
||||||
|
1,
|
||||||
|
33,
|
||||||
|
ac.probe_constant("{ let x = 1 + 2 + 3; x * x }"),
|
||||||
|
);
|
||||||
|
assert_min(&ac, 1, 39, ac.probe_constant(r#""test".len()"#));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_raw() {
|
||||||
|
let ac = autocfg_for_test();
|
||||||
|
let prefix = if ac.no_std() { "#![no_std]\n" } else { "" };
|
||||||
|
let f = |s| format!("{}{}", prefix, s);
|
||||||
|
|
||||||
|
// This attribute **must** be used at the crate level.
|
||||||
|
assert!(ac.probe_raw(&f("#![no_builtins]")).is_ok());
|
||||||
|
|
||||||
|
assert!(ac.probe_raw(&f("#![deny(dead_code)] fn x() {}")).is_err());
|
||||||
|
assert!(ac.probe_raw(&f("#![allow(dead_code)] fn x() {}")).is_ok());
|
||||||
|
assert!(ac
|
||||||
|
.probe_raw(&f("#![deny(dead_code)] pub fn x() {}"))
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn probe_cleanup() {
|
||||||
|
let dir = support::out_dir().join("autocfg_test_probe_cleanup");
|
||||||
|
std::fs::create_dir(&dir).unwrap();
|
||||||
|
|
||||||
|
let ac = AutoCfg::with_dir(&dir).unwrap();
|
||||||
|
assert!(ac.probe_type("i32"));
|
||||||
|
|
||||||
|
// NB: this is not `remove_dir_all`, so it will only work if the directory
|
||||||
|
// is empty -- i.e. the probe should have removed any output files.
|
||||||
|
std::fs::remove_dir(&dir).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn editions() {
|
||||||
|
let mut ac = autocfg_for_test();
|
||||||
|
assert!(ac.edition().is_none());
|
||||||
|
assert!(ac.probe_raw("").is_ok());
|
||||||
|
|
||||||
|
for (edition, minor) in vec![(2015, 27), (2018, 31), (2021, 56), (2024, 85)] {
|
||||||
|
let edition = edition.to_string();
|
||||||
|
ac.set_edition(Some(edition.clone()));
|
||||||
|
assert_eq!(ac.edition(), Some(&*edition));
|
||||||
|
assert_min(&ac, 1, minor, ac.probe_raw("").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
ac.set_edition(Some("invalid".into()));
|
||||||
|
assert_eq!(ac.edition(), Some("invalid"));
|
||||||
|
assert!(ac.probe_raw("").is_err());
|
||||||
|
|
||||||
|
ac.set_edition(None);
|
||||||
|
assert!(ac.edition().is_none());
|
||||||
|
assert!(ac.probe_raw("").is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn edition_keyword_try() {
|
||||||
|
let mut ac = autocfg_for_test();
|
||||||
|
|
||||||
|
if ac.probe_rustc_version(1, 27) {
|
||||||
|
ac.set_edition(Some(2015.to_string()));
|
||||||
|
}
|
||||||
|
assert!(ac.probe_expression("{ let try = 0; try }"));
|
||||||
|
|
||||||
|
if ac.probe_rustc_version(1, 31) {
|
||||||
|
ac.set_edition(Some(2018.to_string()));
|
||||||
|
assert!(!ac.probe_expression("{ let try = 0; try }"));
|
||||||
|
assert!(ac.probe_expression("{ let r#try = 0; r#try }"));
|
||||||
|
}
|
||||||
|
}
|
||||||
83
third_party/crates/autocfg-1.5.1/tests/wrappers.rs
vendored
Normal file
83
third_party/crates/autocfg-1.5.1/tests/wrappers.rs
vendored
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
extern crate autocfg;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
mod support;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<_> = env::args().skip(1).collect();
|
||||||
|
if args.is_empty() {
|
||||||
|
// With no arguments, this must be a (harness-free) test run.
|
||||||
|
// (we're using system binaries as wrappers, so ignore other platforms)
|
||||||
|
if cfg!(unix) {
|
||||||
|
test_wrappers();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise we're acting like a no-op rustc wrapper, ignoring almost everything.
|
||||||
|
for arg in args {
|
||||||
|
match &*arg {
|
||||||
|
// Add our own version so we can check that the wrapper is used for that.
|
||||||
|
"--version" => println!("release: 12345.6789.0"),
|
||||||
|
|
||||||
|
// Read all input so the writer doesn't get EPIPE when we exit.
|
||||||
|
"-" => {
|
||||||
|
let stdin = io::stdin();
|
||||||
|
let _ = io::copy(&mut stdin.lock(), &mut io::sink());
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tests that autocfg uses the RUSTC_WRAPPER and/or RUSTC_WORKSPACE_WRAPPER
|
||||||
|
/// environment variables when running rustc.
|
||||||
|
fn test_wrappers() {
|
||||||
|
fn set(name: &str, value: Option<bool>) {
|
||||||
|
match value {
|
||||||
|
Some(true) => env::set_var(name, "/usr/bin/env"),
|
||||||
|
Some(false) => env::set_var(name, "/bin/false"),
|
||||||
|
None => env::remove_var(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let out = support::out_dir();
|
||||||
|
|
||||||
|
// This is used as a heuristic to detect rust-lang/cargo#9601.
|
||||||
|
env::set_var("CARGO_ENCODED_RUSTFLAGS", "");
|
||||||
|
|
||||||
|
// No wrapper, a good pass-through wrapper, and a bad wrapper.
|
||||||
|
let variants = [None, Some(true), Some(false)];
|
||||||
|
|
||||||
|
for &workspace in &variants {
|
||||||
|
for &rustc in &variants {
|
||||||
|
set("RUSTC_WRAPPER", rustc);
|
||||||
|
set("RUSTC_WORKSPACE_WRAPPER", workspace);
|
||||||
|
|
||||||
|
let ac = autocfg::AutoCfg::with_dir(out.as_ref()).unwrap();
|
||||||
|
if rustc == Some(false) || workspace == Some(false) {
|
||||||
|
// Everything should fail with bad wrappers.
|
||||||
|
assert!(!ac.probe_type("usize"));
|
||||||
|
} else {
|
||||||
|
// Try known good and bad types for the wrapped rustc.
|
||||||
|
assert!(ac.probe_type("usize"));
|
||||||
|
assert!(!ac.probe_type("mesize"));
|
||||||
|
}
|
||||||
|
// Either way, we should have found the inner rustc version.
|
||||||
|
assert!(ac.probe_rustc_version(1, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, make sure that `RUSTC_WRAPPER` is applied outermost
|
||||||
|
// by using something (ourself) that doesn't pass through at all.
|
||||||
|
let wrap_ignored = env::current_exe().unwrap();
|
||||||
|
env::set_var("RUSTC_WRAPPER", &wrap_ignored);
|
||||||
|
env::set_var("RUSTC_WORKSPACE_WRAPPER", "/bin/false");
|
||||||
|
let ac = autocfg::AutoCfg::with_dir(out.as_ref()).unwrap();
|
||||||
|
assert!(ac.probe_type("mesize")); // anything goes!
|
||||||
|
|
||||||
|
// Make sure we also got the version from that wrapper.
|
||||||
|
assert!(ac.probe_rustc_version(12345, 6789));
|
||||||
|
}
|
||||||
8
third_party/crates/crates.bzl
vendored
8
third_party/crates/crates.bzl
vendored
@@ -341,6 +341,7 @@ _NORMAL_DEPENDENCIES = {
|
|||||||
_COMMON_CONDITION: {
|
_COMMON_CONDITION: {
|
||||||
"clap": Label("//third_party/crates/clap-4.6.7:clap"),
|
"clap": Label("//third_party/crates/clap-4.6.7:clap"),
|
||||||
"rnix": Label("//third_party/crates/rnix-0.14.0:rnix"),
|
"rnix": Label("//third_party/crates/rnix-0.14.0:rnix"),
|
||||||
|
"rowan": Label("//third_party/crates/rowan-0.16.1:rowan"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -378,6 +379,9 @@ _NORMAL_DEV_DEPENDENCIES = {
|
|||||||
"src/status": {
|
"src/status": {
|
||||||
},
|
},
|
||||||
"src/xatu": {
|
"src/xatu": {
|
||||||
|
_COMMON_CONDITION: {
|
||||||
|
"googletest": Label("//third_party/crates/googletest-0.14.3:googletest"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,6 +397,8 @@ _NORMAL_DEV_ALIASES = {
|
|||||||
"src/status": {
|
"src/status": {
|
||||||
},
|
},
|
||||||
"src/xatu": {
|
"src/xatu": {
|
||||||
|
_COMMON_CONDITION: {
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +459,8 @@ _PROC_MACRO_DEV_ALIASES = {
|
|||||||
"src/status": {
|
"src/status": {
|
||||||
},
|
},
|
||||||
"src/xatu": {
|
"src/xatu": {
|
||||||
|
_COMMON_CONDITION: {
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
third_party/crates/googletest-0.14.3/.cargo-checksum.json
vendored
Normal file
1
third_party/crates/googletest-0.14.3/.cargo-checksum.json
vendored
Normal file
File diff suppressed because one or more lines are too long
6
third_party/crates/googletest-0.14.3/.cargo_vcs_info.json
vendored
Normal file
6
third_party/crates/googletest-0.14.3/.cargo_vcs_info.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"git": {
|
||||||
|
"sha1": "871f28b74e91e1d2a38e139ea03a9809d9d85f10"
|
||||||
|
},
|
||||||
|
"path_in_vcs": "googletest"
|
||||||
|
}
|
||||||
118
third_party/crates/googletest-0.14.3/BUILD.bazel
vendored
Normal file
118
third_party/crates/googletest-0.14.3/BUILD.bazel
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
###############################################################################
|
||||||
|
# @generated
|
||||||
|
# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To
|
||||||
|
# regenerate this file, run the following:
|
||||||
|
#
|
||||||
|
# bazel run @@//third_party:vendor_crates
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
load("@rules_rust//cargo:defs.bzl", "cargo_toml_env_vars")
|
||||||
|
load("@rules_rust//rust:defs.bzl", "rust_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
cargo_toml_env_vars(
|
||||||
|
name = "cargo_toml_env_vars",
|
||||||
|
src = "Cargo.toml",
|
||||||
|
)
|
||||||
|
|
||||||
|
rust_library(
|
||||||
|
name = "googletest",
|
||||||
|
srcs = glob(
|
||||||
|
include = ["**/*.rs"],
|
||||||
|
allow_empty = False,
|
||||||
|
),
|
||||||
|
compile_data = glob(
|
||||||
|
include = ["**"],
|
||||||
|
allow_empty = True,
|
||||||
|
exclude = [
|
||||||
|
"**/* *",
|
||||||
|
".tmp_git_root/**/*",
|
||||||
|
"BUILD",
|
||||||
|
"BUILD.bazel",
|
||||||
|
"WORKSPACE",
|
||||||
|
"WORKSPACE.bazel",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
crate_root = "src/lib.rs",
|
||||||
|
edition = "2021",
|
||||||
|
proc_macro_deps = [
|
||||||
|
"//third_party/crates/googletest_macro-0.14.3:googletest_macro",
|
||||||
|
"//third_party/crates/rustversion-1.0.23:rustversion",
|
||||||
|
],
|
||||||
|
rustc_env_files = [
|
||||||
|
":cargo_toml_env_vars",
|
||||||
|
],
|
||||||
|
rustc_flags = [
|
||||||
|
"--cap-lints=allow",
|
||||||
|
],
|
||||||
|
tags = [
|
||||||
|
"cargo-bazel",
|
||||||
|
"crate-name=googletest",
|
||||||
|
"manual",
|
||||||
|
"noclippy",
|
||||||
|
"norustfmt",
|
||||||
|
],
|
||||||
|
target_compatible_with = select({
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-darwin": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-ios": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-ios-macabi": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-apple-ios-sim": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-linux-android": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-pc-windows-msvc": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-fuchsia": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-nixos-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-none": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-nto-qnx710": [],
|
||||||
|
"@rules_rust//rust/platform:aarch64-unknown-uefi": [],
|
||||||
|
"@rules_rust//rust/platform:arm-unknown-linux-gnueabi": [],
|
||||||
|
"@rules_rust//rust/platform:arm-unknown-linux-musleabi": [],
|
||||||
|
"@rules_rust//rust/platform:armv7-linux-androideabi": [],
|
||||||
|
"@rules_rust//rust/platform:armv7-unknown-linux-gnueabi": [],
|
||||||
|
"@rules_rust//rust/platform:i686-apple-darwin": [],
|
||||||
|
"@rules_rust//rust/platform:i686-linux-android": [],
|
||||||
|
"@rules_rust//rust/platform:i686-pc-windows-msvc": [],
|
||||||
|
"@rules_rust//rust/platform:i686-unknown-freebsd": [],
|
||||||
|
"@rules_rust//rust/platform:i686-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:loongarch64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:mips-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:powerpc-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:riscv32imac-unknown-none-elf": [],
|
||||||
|
"@rules_rust//rust/platform:riscv32imc-unknown-none-elf": [],
|
||||||
|
"@rules_rust//rust/platform:riscv64gc-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:riscv64gc-unknown-none-elf": [],
|
||||||
|
"@rules_rust//rust/platform:s390x-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:sparc64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:sparc64-unknown-netbsd": [],
|
||||||
|
"@rules_rust//rust/platform:sparc64-unknown-openbsd": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv6m-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv7em-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv7em-none-eabihf": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv7m-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv8m.main-none-eabi": [],
|
||||||
|
"@rules_rust//rust/platform:thumbv8m.main-none-eabihf": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-unknown-emscripten": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-unknown-unknown": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-wasip1": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-wasip1-threads": [],
|
||||||
|
"@rules_rust//rust/platform:wasm32-wasip2": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-apple-darwin": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-apple-ios": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-apple-ios-macabi": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-linux-android": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-pc-windows-msvc": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-freebsd": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-fuchsia": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-linux-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-nixos-gnu": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-none": [],
|
||||||
|
"@rules_rust//rust/platform:x86_64-unknown-uefi": [],
|
||||||
|
"//conditions:default": ["@platforms//:incompatible"],
|
||||||
|
}),
|
||||||
|
version = "0.14.3",
|
||||||
|
deps = [
|
||||||
|
"//third_party/crates/num-traits-0.2.19:num_traits",
|
||||||
|
"//third_party/crates/regex-1.13.1:regex",
|
||||||
|
],
|
||||||
|
)
|
||||||
527
third_party/crates/googletest-0.14.3/Cargo.lock
generated
vendored
Normal file
527
third_party/crates/googletest-0.14.3/Cargo.lock
generated
vendored
Normal file
@@ -0,0 +1,527 @@
|
|||||||
|
# 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.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-set"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||||
|
dependencies = [
|
||||||
|
"bit-vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-vec"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.13.3+wasi-0.2.2",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "googletest"
|
||||||
|
version = "0.14.3"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"googletest_macro",
|
||||||
|
"indoc",
|
||||||
|
"num-traits",
|
||||||
|
"proptest",
|
||||||
|
"quickcheck",
|
||||||
|
"regex",
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "googletest_macro"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ae6abc96141edd26bf5aeec0f119c129c44de3ced09e5073711a02cb74725d0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indoc"
|
||||||
|
version = "2.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.178"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proptest"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40"
|
||||||
|
dependencies = [
|
||||||
|
"bit-set",
|
||||||
|
"bit-vec",
|
||||||
|
"bitflags",
|
||||||
|
"num-traits",
|
||||||
|
"rand 0.9.1",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_xorshift",
|
||||||
|
"regex-syntax",
|
||||||
|
"rusty-fork",
|
||||||
|
"tempfile",
|
||||||
|
"unarray",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quickcheck"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core 0.9.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.9.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.15",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_xorshift"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.9.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusty-fork"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"quick-error",
|
||||||
|
"tempfile",
|
||||||
|
"wait-timeout",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.114"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom 0.3.1",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unarray"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wait-timeout"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.13.3+wasi-0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[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_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[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_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.7.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.7.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
160
third_party/crates/googletest-0.14.3/Cargo.toml
vendored
Normal file
160
third_party/crates/googletest-0.14.3/Cargo.toml
vendored
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||||
|
#
|
||||||
|
# When uploading crates to the registry Cargo will automatically
|
||||||
|
# "normalize" Cargo.toml files for maximal compatibility
|
||||||
|
# with all versions of Cargo and also rewrite `path` dependencies
|
||||||
|
# to registry (e.g., crates.io) dependencies.
|
||||||
|
#
|
||||||
|
# If you are reading this file be aware that the original Cargo.toml
|
||||||
|
# will likely look very different (and much more reasonable).
|
||||||
|
# See Cargo.toml.orig for the original contents.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.85.0"
|
||||||
|
name = "googletest"
|
||||||
|
version = "0.14.3"
|
||||||
|
authors = [
|
||||||
|
"Bradford Hovinen <hovinen@google.com>",
|
||||||
|
"Bastien Jacot-Guillarmod <bjacotg@google.com>",
|
||||||
|
"Maciej Pietrzak <mpi@google.com>",
|
||||||
|
"Martin Geisler <mgeisler@google.com>",
|
||||||
|
]
|
||||||
|
build = false
|
||||||
|
autolib = false
|
||||||
|
autobins = false
|
||||||
|
autoexamples = false
|
||||||
|
autotests = false
|
||||||
|
autobenches = false
|
||||||
|
description = "A rich assertion and matcher library inspired by GoogleTest for C++"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = [
|
||||||
|
"unit",
|
||||||
|
"matcher",
|
||||||
|
"testing",
|
||||||
|
"assertions",
|
||||||
|
]
|
||||||
|
categories = [
|
||||||
|
"development-tools",
|
||||||
|
"development-tools::testing",
|
||||||
|
]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
repository = "https://github.com/google/googletest-rust"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "googletest"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "all_matcher_test"
|
||||||
|
path = "tests/all_matcher_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "any_matcher_test"
|
||||||
|
path = "tests/any_matcher_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "assertions_test"
|
||||||
|
path = "tests/assertions_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "colorized_diff_test"
|
||||||
|
path = "tests/colorized_diff_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "composition_test"
|
||||||
|
path = "tests/composition_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "elements_are_matcher_test"
|
||||||
|
path = "tests/elements_are_matcher_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "field_matcher_test"
|
||||||
|
path = "tests/field_matcher_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "fmt_test"
|
||||||
|
path = "tests/fmt_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "matches_pattern_enum_test"
|
||||||
|
path = "tests/matches_pattern_enum_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "matches_pattern_struct_and_enum_test"
|
||||||
|
path = "tests/matches_pattern_struct_and_enum_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "matches_pattern_struct_test"
|
||||||
|
path = "tests/matches_pattern_struct_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "matches_pattern_tuple_struct_test"
|
||||||
|
path = "tests/matches_pattern_tuple_struct_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "no_color_test"
|
||||||
|
path = "tests/no_color_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "no_std_test"
|
||||||
|
path = "tests/no_std_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "pointwise_matcher_test"
|
||||||
|
path = "tests/pointwise_matcher_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "property_matcher_test"
|
||||||
|
path = "tests/property_matcher_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "proptest_integration_test"
|
||||||
|
path = "tests/proptest_integration_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "reference_type_test"
|
||||||
|
path = "tests/reference_type_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "scoped_trace_panic_test"
|
||||||
|
path = "tests/scoped_trace_panic_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "scoped_trace_test"
|
||||||
|
path = "tests/scoped_trace_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "tuple_matcher_test"
|
||||||
|
path = "tests/tuple_matcher_test.rs"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "unordered_elements_are_matcher_test"
|
||||||
|
path = "tests/unordered_elements_are_matcher_test.rs"
|
||||||
|
|
||||||
|
[dependencies.anyhow]
|
||||||
|
version = "1"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.googletest_macro]
|
||||||
|
version = "0.14.3"
|
||||||
|
|
||||||
|
[dependencies.num-traits]
|
||||||
|
version = "0.2.17"
|
||||||
|
|
||||||
|
[dependencies.proptest]
|
||||||
|
version = "1.9.0"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[dependencies.regex]
|
||||||
|
version = "1.12.2"
|
||||||
|
|
||||||
|
[dependencies.rustversion]
|
||||||
|
version = "1.0.22"
|
||||||
|
|
||||||
|
[dev-dependencies.indoc]
|
||||||
|
version = "2"
|
||||||
|
|
||||||
|
[dev-dependencies.quickcheck]
|
||||||
|
version = "1.0.3"
|
||||||
43
third_party/crates/googletest-0.14.3/Cargo.toml.orig
generated
vendored
Normal file
43
third_party/crates/googletest-0.14.3/Cargo.toml.orig
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Copyright 2022 Google LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "googletest"
|
||||||
|
version = "0.14.3"
|
||||||
|
keywords = ["unit", "matcher", "testing", "assertions"]
|
||||||
|
categories = ["development-tools", "development-tools::testing"]
|
||||||
|
description = "A rich assertion and matcher library inspired by GoogleTest for C++"
|
||||||
|
repository = "https://github.com/google/googletest-rust"
|
||||||
|
readme = "../README.md"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.85.0" # Needed for async closures.
|
||||||
|
authors = [
|
||||||
|
"Bradford Hovinen <hovinen@google.com>",
|
||||||
|
"Bastien Jacot-Guillarmod <bjacotg@google.com>",
|
||||||
|
"Maciej Pietrzak <mpi@google.com>",
|
||||||
|
"Martin Geisler <mgeisler@google.com>",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
googletest_macro = { path = "../googletest_macro", version = "0.14.3" }
|
||||||
|
anyhow = { version = "1", optional = true }
|
||||||
|
num-traits = "0.2.17"
|
||||||
|
proptest = { version = "1.9.0", optional = true }
|
||||||
|
regex = "1.12.2"
|
||||||
|
rustversion = "1.0.22"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
indoc = "2"
|
||||||
|
quickcheck = "1.0.3"
|
||||||
202
third_party/crates/googletest-0.14.3/LICENSE
vendored
Normal file
202
third_party/crates/googletest-0.14.3/LICENSE
vendored
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
367
third_party/crates/googletest-0.14.3/README.md
vendored
Normal file
367
third_party/crates/googletest-0.14.3/README.md
vendored
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
# GoogleTest Rust
|
||||||
|
|
||||||
|
[![crates.io][crates-badge]][crates-url]
|
||||||
|
[![docs.rs][docs-badge]][docs-url]
|
||||||
|
[![Apache licensed][license-badge]][license-url]
|
||||||
|
[![Build Status][actions-badge]][actions-url]
|
||||||
|
[![OpenSSF Best Practices][openssf-badge]][openssf-url]
|
||||||
|
|
||||||
|
[crates-badge]: https://img.shields.io/crates/v/googletest.svg
|
||||||
|
[crates-url]: https://crates.io/crates/googletest
|
||||||
|
[docs-badge]: https://img.shields.io/badge/docs.rs-googletest-66c2a5
|
||||||
|
[docs-url]: https://docs.rs/googletest/*/googletest/
|
||||||
|
[license-badge]: https://img.shields.io/badge/license-Apache-blue.svg
|
||||||
|
[license-url]: https://github.com/google/googletest-rust/blob/main/LICENSE
|
||||||
|
[actions-badge]: https://github.com/google/googletest-rust/workflows/CI/badge.svg
|
||||||
|
[actions-url]: https://github.com/google/googletest-rust/actions?query=workflow%3ACI+branch%3Amain
|
||||||
|
[openssf-badge]: https://www.bestpractices.dev/projects/10037/badge
|
||||||
|
[openssf-url]: https://www.bestpractices.dev/projects/10037
|
||||||
|
|
||||||
|
This library brings the rich assertion types of Google's C++ testing library
|
||||||
|
[GoogleTest](https://github.com/google/googletest) to Rust. It provides:
|
||||||
|
|
||||||
|
* A framework for writing matchers which can be combined to make a wide range
|
||||||
|
of assertions on data,
|
||||||
|
* A rich set of matchers providing similar functionality to those included in
|
||||||
|
[GoogleTest](https://google.github.io/googletest/reference/matchers.html),
|
||||||
|
and
|
||||||
|
* A new set of assertion macros offering similar functionality to those of
|
||||||
|
[GoogleTest](https://google.github.io/googletest/primer.html#assertions).
|
||||||
|
|
||||||
|
**The minimum supported Rust version is 1.85**.
|
||||||
|
|
||||||
|
> :warning: The API is not fully stable and may still be changed until we
|
||||||
|
> publish version 1.0.
|
||||||
|
>
|
||||||
|
> Moreover, any items or modules starting with `__` (double underscores) must
|
||||||
|
> not be used directly. Those items or modules are only for internal uses and
|
||||||
|
> their API may change without a major version update.
|
||||||
|
|
||||||
|
## Learning resources
|
||||||
|
|
||||||
|
If you're just getting started with `googletest`, consider going through
|
||||||
|
the first chapter of
|
||||||
|
["Advanced testing for Rust applications"](https://github.com/mainmatter/rust-advanced-testing-workshop),
|
||||||
|
a self-guided Rust course: it provides a guided introduction to the library,
|
||||||
|
with exercises to help you get comfortable with `googletest` macros,
|
||||||
|
its matchers and its overall philosophy.
|
||||||
|
|
||||||
|
## Assertions and matchers
|
||||||
|
|
||||||
|
The core of GoogleTest is its *matchers*. Matchers indicate what aspect of an
|
||||||
|
actual value one is asserting: (in-)equality, containment, regular expression
|
||||||
|
matching, and so on.
|
||||||
|
|
||||||
|
To make an assertion using a matcher, GoogleTest offers three macros:
|
||||||
|
|
||||||
|
* [`assert_that!`] panics if the assertion fails, aborting the test.
|
||||||
|
* [`expect_that!`] logs an assertion failure, marking the test as having
|
||||||
|
failed, but allows the test to continue running (called a _non-fatal
|
||||||
|
assertion_). It requires the use of the [`gtest`] attribute macro
|
||||||
|
on the test itself.
|
||||||
|
* [`verify_that!`] has no side effects and evaluates to a [`Result<()>`] whose
|
||||||
|
`Err` variant describes the assertion failure, if there is one. In
|
||||||
|
combination with the
|
||||||
|
[`?` operator](https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator),
|
||||||
|
this can be used to abort the test on assertion failure without panicking. It
|
||||||
|
is also the building block for the other two macros above.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_and_panics() {
|
||||||
|
let value = 2;
|
||||||
|
assert_that!(value, eq(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtest]
|
||||||
|
fn two_logged_failures() {
|
||||||
|
let value = 2;
|
||||||
|
expect_that!(value, eq(4)); // Test now failed, but continues executing.
|
||||||
|
expect_that!(value, eq(5)); // Second failure is also logged.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fails_immediately_without_panic() -> Result<()> {
|
||||||
|
let value = 2;
|
||||||
|
verify_that!(value, eq(4))?; // Test fails and aborts.
|
||||||
|
verify_that!(value, eq(2))?; // Never executes.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_assertion() -> Result<()> {
|
||||||
|
let value = 2;
|
||||||
|
verify_that!(value, eq(4)) // One can also just return the last assertion.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This library includes a rich set of matchers, covering:
|
||||||
|
|
||||||
|
* Equality, numeric inequality, and approximate equality;
|
||||||
|
* Strings and regular expressions;
|
||||||
|
* Containers and set-theoretic matching.
|
||||||
|
|
||||||
|
Matchers are composable:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
#[gtest]
|
||||||
|
fn contains_at_least_one_item_at_least_3() {
|
||||||
|
let value = vec![1, 2, 3];
|
||||||
|
expect_that!(value, contains(ge(3)));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
They can also be logically combined:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
#[gtest]
|
||||||
|
fn strictly_between_9_and_11() {
|
||||||
|
let value = 10;
|
||||||
|
expect_that!(value, gt(9).and(not(ge(11))));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pattern-matching
|
||||||
|
|
||||||
|
One can use the macro [`matches_pattern!`] to create a composite matcher for a
|
||||||
|
struct or enum that matches fields with other matchers:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
struct AStruct {
|
||||||
|
a_field: i32,
|
||||||
|
another_field: i32,
|
||||||
|
a_third_field: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_has_expected_values() {
|
||||||
|
let value = AStruct {
|
||||||
|
a_field: 10,
|
||||||
|
another_field: 100,
|
||||||
|
a_third_field: "A correct value",
|
||||||
|
};
|
||||||
|
expect_that!(value, matches_pattern!(AStruct {
|
||||||
|
a_field: eq(10),
|
||||||
|
another_field: gt(50),
|
||||||
|
a_third_field: contains_substring("correct"),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing matchers
|
||||||
|
|
||||||
|
One can extend the library by writing additional matchers. To do so, create a
|
||||||
|
struct holding the matcher's data and have it implement the trait [`Matcher`]:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
struct MyEqMatcher<T> {
|
||||||
|
expected: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq + Debug + Copy> Matcher<T> for MyEqMatcher<T> {
|
||||||
|
fn matches(&self, actual: T) -> MatcherResult {
|
||||||
|
(self.expected == actual).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe(&self, matcher_result: MatcherResult) -> String {
|
||||||
|
match matcher_result {
|
||||||
|
MatcherResult::Match => {
|
||||||
|
format!("is equal to {:?} the way I define it", self.expected)
|
||||||
|
}
|
||||||
|
MatcherResult::NoMatch => {
|
||||||
|
format!("isn't equal to {:?} the way I define it", self.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is recommended to expose a function which constructs the matcher:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn eq_my_way<T: PartialEq + Debug>(expected: T) -> impl Matcher<T> {
|
||||||
|
MyEqMatcher { expected }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The new matcher can then be used in the assertion macros:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[gtest]
|
||||||
|
fn should_be_equal_by_my_definition() {
|
||||||
|
expect_that!(10, eq_my_way(10));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-fatal assertions
|
||||||
|
|
||||||
|
Using non-fatal assertions, a single test is able to log multiple assertion
|
||||||
|
failures. Any single assertion failure causes the test to be considered having
|
||||||
|
failed, but execution continues until the test completes or otherwise aborts.
|
||||||
|
|
||||||
|
This is analogous to the `EXPECT_*` family of macros in GoogleTest.
|
||||||
|
|
||||||
|
To make a non-fatal assertion, use the macro [`expect_that!`]. The test must
|
||||||
|
also be marked with [`gtest`] instead of the Rust-standard `#[test]`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
#[gtest]
|
||||||
|
fn three_non_fatal_assertions() {
|
||||||
|
let value = 2;
|
||||||
|
expect_that!(value, eq(2)); // Passes; test still considered passing.
|
||||||
|
expect_that!(value, eq(3)); // Fails; logs failure and marks the test failed.
|
||||||
|
expect_that!(value, eq(4)); // A second failure, also logged.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be used in the same tests as `verify_that!`, in which case the test
|
||||||
|
function must also return [`Result<()>`]:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
#[gtest]
|
||||||
|
fn failing_non_fatal_assertion() -> Result<()> {
|
||||||
|
let value = 2;
|
||||||
|
expect_that!(value, eq(3)); // Just marks the test as having failed.
|
||||||
|
verify_that!(value, eq(2))?; // Passes, so does not abort the test.
|
||||||
|
Ok(()) // Because of the failing expect_that! call above, the
|
||||||
|
// test fails despite returning Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
#[gtest]
|
||||||
|
fn failing_fatal_assertion_after_non_fatal_assertion() -> Result<()> {
|
||||||
|
let value = 2;
|
||||||
|
verify_that!(value, eq(3))?; // Fails and aborts the test.
|
||||||
|
expect_that!(value, eq(3)); // Never executes, since the test already aborted.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interoperability
|
||||||
|
|
||||||
|
You can use the `#[gtest]` macro together with many other libraries
|
||||||
|
such as [rstest](https://crates.io/crates/rstest). Just apply both attribute
|
||||||
|
macros to the test:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[gtest]
|
||||||
|
#[rstest]
|
||||||
|
#[case(1)]
|
||||||
|
#[case(2)]
|
||||||
|
#[case(3)]
|
||||||
|
fn rstest_works_with_google_test(#[case] value: u32) -> Result<()> {
|
||||||
|
verify_that!(value, gt(0))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to put `#[gtest]` *before* `#[rstest]`. Otherwise the
|
||||||
|
annotated test will run twice, since both macros will attempt to register a test
|
||||||
|
with the Rust test harness.
|
||||||
|
|
||||||
|
The macro also works together with
|
||||||
|
[async tests with Tokio](https://docs.rs/tokio/latest/tokio/attr.gtest.html) in
|
||||||
|
the same way:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[gtest]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn should_work_with_tokio() -> Result<()> {
|
||||||
|
verify_that!(3, gt(0))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There is one caveat when running async tests: test failure reporting through
|
||||||
|
`and_log_failure` will not work properly if the assertion occurs on a different
|
||||||
|
thread than runs the test.
|
||||||
|
|
||||||
|
## Predicate assertions
|
||||||
|
|
||||||
|
The macro [`verify_pred!`] provides predicate assertions analogous to
|
||||||
|
GoogleTest's `EXPECT_PRED` family of macros. Wrap an invocation of a predicate
|
||||||
|
in a `verify_pred!` invocation to turn that into a test assertion which passes
|
||||||
|
precisely when the predicate returns `true`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn stuff_is_correct(x: i32, y: i32) -> bool {
|
||||||
|
x == y
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = 3;
|
||||||
|
let y = 4;
|
||||||
|
verify_pred!(stuff_is_correct(x, y))?;
|
||||||
|
```
|
||||||
|
|
||||||
|
The assertion failure message shows the arguments and the values to which they
|
||||||
|
evaluate:
|
||||||
|
|
||||||
|
```
|
||||||
|
stuff_is_correct(x, y) was false with
|
||||||
|
x = 3,
|
||||||
|
y = 4
|
||||||
|
```
|
||||||
|
|
||||||
|
The `verify_pred!` invocation evaluates to a [`Result<()>`] just like
|
||||||
|
[`verify_that!`]. There is also a macro [`expect_pred!`] to make a non-fatal
|
||||||
|
predicaticate assertion.
|
||||||
|
|
||||||
|
## Unconditionally generating a test failure
|
||||||
|
|
||||||
|
The macro [`fail!`] unconditionally evaluates to a `Result` indicating a test
|
||||||
|
failure. It can be used analogously to [`verify_that!`] and [`verify_pred!`] to
|
||||||
|
cause a test to fail, with an optional formatted message:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn always_fails() -> Result<()> {
|
||||||
|
fail!("This test must fail with {}", "today")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
This library is configurable through environment variables. Since the
|
||||||
|
configuration does not impact whether a test fails or not but how a failure is
|
||||||
|
displayed, we recommend setting those variables in the personal
|
||||||
|
`~/.cargo/config.toml` instead of in the project-scoped `Cargo.toml`.
|
||||||
|
|
||||||
|
### Configuration variable list
|
||||||
|
|
||||||
|
| Variable name | Description |
|
||||||
|
| ------------- | ------------------------------------------------------- |
|
||||||
|
| NO_COLOR | Disables colored output. See <https://no-color.org/>. |
|
||||||
|
| FORCE_COLOR | Forces colors even when the output is piped to a file. |
|
||||||
|
|
||||||
|
## Contributing Changes
|
||||||
|
|
||||||
|
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
|
||||||
|
to this project.
|
||||||
|
|
||||||
|
[`and_log_failure()`]: https://docs.rs/googletest/*/googletest/trait.GoogleTestSupport.html#tymethod.and_log_failure
|
||||||
|
[`assert_that!`]: https://docs.rs/googletest/*/googletest/macro.assert_that.html
|
||||||
|
[`expect_pred!`]: https://docs.rs/googletest/*/googletest/macro.expect_pred.html
|
||||||
|
[`expect_that!`]: https://docs.rs/googletest/*/googletest/macro.expect_that.html
|
||||||
|
[`fail!`]: https://docs.rs/googletest/*/googletest/macro.fail.html
|
||||||
|
[`gtest`]: https://docs.rs/googletest/*/googletest/attr.gtest.html
|
||||||
|
[`matches_pattern!`]: https://docs.rs/googletest/*/googletest/macro.matches_pattern.html
|
||||||
|
[`verify_pred!`]: https://docs.rs/googletest/*/googletest/macro.verify_pred.html
|
||||||
|
[`verify_that!`]: https://docs.rs/googletest/*/googletest/macro.verify_that.html
|
||||||
|
[`Describe`]: https://docs.rs/googletest/*/googletest/matcher/trait.Describe.html
|
||||||
|
[`Matcher`]: https://docs.rs/googletest/*/googletest/matcher/trait.Matcher.html
|
||||||
|
[`Result<()>`]: https://docs.rs/googletest/*/googletest/type.Result.html
|
||||||
544
third_party/crates/googletest-0.14.3/crate_docs.md
vendored
Normal file
544
third_party/crates/googletest-0.14.3/crate_docs.md
vendored
Normal file
@@ -0,0 +1,544 @@
|
|||||||
|
A rich test assertion library for Rust.
|
||||||
|
|
||||||
|
This library provides:
|
||||||
|
|
||||||
|
* A framework for writing matchers which can be combined to make a wide
|
||||||
|
range of assertions on data,
|
||||||
|
* A rich set of matchers, and
|
||||||
|
* A new set of test assertion macros.
|
||||||
|
## Learning resources
|
||||||
|
|
||||||
|
If you're just getting started with `googletest`, consider going through
|
||||||
|
the first chapter of
|
||||||
|
["Advanced testing for Rust applications"](https://github.com/mainmatter/rust-advanced-testing-workshop),
|
||||||
|
a self-guided Rust course: it provides a guided introduction to the library,
|
||||||
|
with exercises to help you get comfortable with `googletest` macros,
|
||||||
|
its matchers and its overall philosophy.
|
||||||
|
|
||||||
|
## Assertions and matchers
|
||||||
|
|
||||||
|
The core of GoogleTest is its *matchers*. Matchers indicate what aspect of an
|
||||||
|
actual value one is asserting: (in-)equality, containment, regular expression
|
||||||
|
matching, and so on.
|
||||||
|
|
||||||
|
To make an assertion using a matcher, GoogleTest offers three macros:
|
||||||
|
|
||||||
|
* [`assert_that!`] panics if the assertion fails, aborting the test.
|
||||||
|
* [`expect_that!`] logs an assertion failure, marking the test as having
|
||||||
|
failed, but allows the test to continue running (called a _non-fatal
|
||||||
|
assertion_). It requires the use of the [`gtest`]
|
||||||
|
attribute macro on the test itself.
|
||||||
|
* [`verify_that!`] has no side effects and evaluates to a [`Result`] whose
|
||||||
|
`Err` variant describes the assertion failure, if there is one. In
|
||||||
|
combination with the
|
||||||
|
[`?` operator](https://doc.rust-lang.org/reference/expressions/operator-expr.html#the-question-mark-operator),
|
||||||
|
this can be used to abort the test on assertion failure without panicking. It
|
||||||
|
is also the building block for the other two macros above.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
# /* The attribute macro would prevent the function from being compiled in a doctest.
|
||||||
|
#[test]
|
||||||
|
# */
|
||||||
|
fn fails_and_panics() {
|
||||||
|
let value = 2;
|
||||||
|
assert_that!(value, eq(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
# /* The attribute macro would prevent the function from being compiled in a doctest.
|
||||||
|
#[gtest]
|
||||||
|
# */
|
||||||
|
fn two_logged_failures() {
|
||||||
|
let value = 2;
|
||||||
|
expect_that!(value, eq(4)); // Test now failed, but continues executing.
|
||||||
|
expect_that!(value, eq(5)); // Second failure is also logged.
|
||||||
|
}
|
||||||
|
|
||||||
|
# /* The attribute macro would prevent the function from being compiled in a doctest.
|
||||||
|
#[test]
|
||||||
|
# */
|
||||||
|
fn fails_immediately_without_panic() -> Result<()> {
|
||||||
|
let value = 2;
|
||||||
|
verify_that!(value, eq(4))?; // Test fails and aborts.
|
||||||
|
verify_that!(value, eq(2))?; // Never executes.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
# /* The attribute macro would prevent the function from being compiled in a doctest.
|
||||||
|
#[test]
|
||||||
|
# */
|
||||||
|
fn simple_assertion() -> Result<()> {
|
||||||
|
let value = 2;
|
||||||
|
verify_that!(value, eq(4)) // One can also just return the last assertion.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Matchers are composable:
|
||||||
|
|
||||||
|
```
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
# /* The attribute macro would prevent the function from being compiled in a doctest.
|
||||||
|
#[gtest]
|
||||||
|
# */
|
||||||
|
fn contains_at_least_one_item_at_least_3() {
|
||||||
|
# googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
|
||||||
|
let value = vec![1, 2, 3];
|
||||||
|
expect_that!(value, contains(ge(&3)));
|
||||||
|
# googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
|
||||||
|
# .unwrap();
|
||||||
|
}
|
||||||
|
# contains_at_least_one_item_at_least_3();
|
||||||
|
```
|
||||||
|
|
||||||
|
They can also be logically combined, with methods from [`MatcherBase`]:
|
||||||
|
|
||||||
|
```
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
# /* The attribute macro would prevent the function from being compiled in a doctest.
|
||||||
|
#[gtest]
|
||||||
|
# */
|
||||||
|
fn strictly_between_9_and_11() {
|
||||||
|
# googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
|
||||||
|
let value = 10;
|
||||||
|
expect_that!(value, gt(9).and(not(ge(11))));
|
||||||
|
# googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
|
||||||
|
# .unwrap();
|
||||||
|
}
|
||||||
|
# strictly_between_9_and_11();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available matchers
|
||||||
|
|
||||||
|
The following matchers are provided in GoogleTest Rust:
|
||||||
|
|
||||||
|
| Matcher | What it matches |
|
||||||
|
|----------------------|--------------------------------------------------------------------------|
|
||||||
|
| [`all!`] | Anything matched by all given matchers. |
|
||||||
|
| [`any!`] | Anything matched by at least one of the given matchers. |
|
||||||
|
| [`anything`] | Any input. |
|
||||||
|
| [`approx_eq`] | A floating point number within a standard tolerance of the argument. |
|
||||||
|
| [`char_count`] | A string with a Unicode scalar count matching the argument. |
|
||||||
|
| [`container_eq`] | Same as [`eq`], but for containers (with a better mismatch description). |
|
||||||
|
| [`contains`] | A container containing an element matched by the given matcher. |
|
||||||
|
| [`contains_each!`] | A container containing distinct elements each of the arguments match. |
|
||||||
|
| [`contains_regex`] | A string containing a substring matching the given regular expression. |
|
||||||
|
| [`contains_substring`] | A string containing the given substring. |
|
||||||
|
| [`derefs_to`] | A [`Deref`] which `deref()`s to a value that the argument matches. |
|
||||||
|
| [`displays_as`] | A [`Display`] value whose formatted string is matched by the argument. |
|
||||||
|
| [`each`] | A container all of whose elements the given argument matches. |
|
||||||
|
| [`elements_are!`] | A container whose elements the arguments match, in order. |
|
||||||
|
| [`ends_with`] | A string ending with the given suffix. |
|
||||||
|
| [`eq`] | A value equal to the argument, in the sense of the [`PartialEq`] trait. |
|
||||||
|
| [`err`] | A [`Result`][std::result::Result] containing an `Err` variant the argument matches. |
|
||||||
|
| [`field!`] | A struct or enum with a given field whose value the argument matches. |
|
||||||
|
| [`ge`] | A [`PartialOrd`] value greater than or equal to the given value. |
|
||||||
|
| [`gt`] | A [`PartialOrd`] value strictly greater than the given value. |
|
||||||
|
| [`has_entry`] | A [`HashMap`] containing a given key whose value the argument matches. |
|
||||||
|
| [`is_contained_in!`] | A container each of whose elements is matched by some given matcher. |
|
||||||
|
| [`is_empty`] | An empty collection. |
|
||||||
|
| [`is_finite`] | A floating point number which is neither infinite nor NaN. |
|
||||||
|
| [`is_infinite`] | A floating point number which is positive or negative infinity. |
|
||||||
|
| [`is_nan`] | A floating point number which is NaN. |
|
||||||
|
| [`le`] | A [`PartialOrd`] value less than or equal to the given value. |
|
||||||
|
| [`len`] | A container whose number of elements the argument matches. |
|
||||||
|
| [`lt`] | A [`PartialOrd`] value strictly less than the given value. |
|
||||||
|
| [`matches_pattern!`] | A struct or enum whose fields are matched according to the arguments. |
|
||||||
|
| [`matches_regex`] | A string matched by the given regular expression. |
|
||||||
|
| [`near`] | A floating point number within a given tolerance of the argument. |
|
||||||
|
| [`none`] | An [`Option`] containing `None`. |
|
||||||
|
| [`not`] | Any value the argument does not match. |
|
||||||
|
| [`ok`] | A [`Result`][std::result::Result] containing an `Ok` variant the argument matches. |
|
||||||
|
| [`pat!`] | Alias for [`matches_pattern!`]. |
|
||||||
|
| [`points_to`] | A reference `&` which points to a value that the argument matches. |
|
||||||
|
| [`pointwise!`] | A container whose contents the arguments match in a pointwise fashion. |
|
||||||
|
| [`predicate`] | A value on which the given predicate returns true. |
|
||||||
|
| [`some`] | An [`Option`] containing `Some` whose value the argument matches. |
|
||||||
|
| [`starts_with`] | A string starting with the given prefix. |
|
||||||
|
| [`subset_of`] | A container all of whose elements are contained in the argument. |
|
||||||
|
| [`superset_of`] | A container containing all elements of the argument. |
|
||||||
|
| [`unordered_elements_are!`] | A container whose elements the arguments match, in any order. |
|
||||||
|
|
||||||
|
[`all!`]: matchers::all
|
||||||
|
[`any!`]: matchers::any
|
||||||
|
[`anything`]: matchers::anything
|
||||||
|
[`approx_eq`]: matchers::approx_eq
|
||||||
|
[`char_count`]: matchers::char_count
|
||||||
|
[`container_eq`]: matchers::container_eq
|
||||||
|
[`contains`]: matchers::contains
|
||||||
|
[`contains_each!`]: matchers::contains_each
|
||||||
|
[`contains_regex`]: matchers::contains_regex
|
||||||
|
[`contains_substring`]: matchers::contains_substring
|
||||||
|
[`displays_as`]: matchers::displays_as
|
||||||
|
[`derefs_to`]: matchers::derefs_to
|
||||||
|
[`each`]: matchers::each
|
||||||
|
[`elements_are!`]: matchers::elements_are
|
||||||
|
[`ends_with`]: matchers::ends_with
|
||||||
|
[`eq`]: matchers::eq
|
||||||
|
[`err`]: matchers::err
|
||||||
|
[`field!`]: matchers::field
|
||||||
|
[`ge`]: matchers::ge
|
||||||
|
[`gt`]: matchers::gt
|
||||||
|
[`has_entry`]: matchers::has_entry
|
||||||
|
[`is_contained_in!`]: matchers::is_contained_in
|
||||||
|
[`is_empty`]: matchers::is_empty
|
||||||
|
[`is_finite`]: matchers::is_finite
|
||||||
|
[`is_infinite`]: matchers::is_infinite
|
||||||
|
[`is_nan`]: matchers::is_nan
|
||||||
|
[`le`]: matchers::le
|
||||||
|
[`len`]: matchers::len
|
||||||
|
[`lt`]: matchers::lt
|
||||||
|
[`matches_regex`]: matchers::matches_regex
|
||||||
|
[`matches_pattern!`]: matchers::matches_pattern
|
||||||
|
[`near`]: matchers::near
|
||||||
|
[`none`]: matchers::none
|
||||||
|
[`not`]: matchers::not
|
||||||
|
[`pat!`]: matchers::pat
|
||||||
|
[`ok`]: matchers::ok
|
||||||
|
[`points_to`]: matchers::points_to
|
||||||
|
[`pointwise!`]: matchers::pointwise
|
||||||
|
[`predicate`]: matchers::predicate
|
||||||
|
[`some`]: matchers::some
|
||||||
|
[`starts_with`]: matchers::starts_with
|
||||||
|
[`subset_of`]: matchers::subset_of
|
||||||
|
[`superset_of`]: matchers::superset_of
|
||||||
|
[`unordered_elements_are!`]: matchers::unordered_elements_are
|
||||||
|
[`Deref`]: std::ops::Deref
|
||||||
|
[`Display`]: std::fmt::Display
|
||||||
|
[`HashMap`]: std::collections::HashMap
|
||||||
|
[`Option`]: std::option::Option
|
||||||
|
[`PartialEq`]: std::cmp::PartialEq
|
||||||
|
[`PartialOrd`]: std::cmp::PartialOrd
|
||||||
|
|
||||||
|
## Writing matchers
|
||||||
|
|
||||||
|
One can extend the library by writing additional matchers. To do so, create
|
||||||
|
a struct holding the matcher's data and have it implement the traits
|
||||||
|
[`Matcher`] and [`MatcherBase`]:
|
||||||
|
|
||||||
|
```no_run
|
||||||
|
use googletest::{description::Description, matcher::{Matcher, MatcherBase, MatcherResult}};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
#[derive(MatcherBase)]
|
||||||
|
struct MyEqMatcher<T> {
|
||||||
|
expected: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq + Debug + Copy> Matcher<T> for MyEqMatcher<T> {
|
||||||
|
fn matches(&self, actual: T) -> MatcherResult {
|
||||||
|
if self.expected == actual {
|
||||||
|
MatcherResult::Match
|
||||||
|
} else {
|
||||||
|
MatcherResult::NoMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||||
|
match matcher_result {
|
||||||
|
MatcherResult::Match => {
|
||||||
|
format!("is equal to {:?} the way I define it", self.expected).into()
|
||||||
|
}
|
||||||
|
MatcherResult::NoMatch => {
|
||||||
|
format!("isn't equal to {:?} the way I define it", self.expected).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is recommended to expose a function which constructs the matcher:
|
||||||
|
|
||||||
|
```no_run
|
||||||
|
# use googletest::{description::Description, matcher::{Matcher, MatcherBase, MatcherResult}};
|
||||||
|
# use std::fmt::Debug;
|
||||||
|
# #[derive(MatcherBase)]
|
||||||
|
# struct MyEqMatcher<T> {
|
||||||
|
# expected: T,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl<T: PartialEq + Debug + Copy> Matcher<T> for MyEqMatcher<T> {
|
||||||
|
# fn matches(&self, actual: T) -> MatcherResult {
|
||||||
|
# if self.expected == actual {
|
||||||
|
# MatcherResult::Match
|
||||||
|
# } else {
|
||||||
|
# MatcherResult::NoMatch
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||||
|
# match matcher_result {
|
||||||
|
# MatcherResult::Match => {
|
||||||
|
# format!("is equal to {:?} the way I define it", self.expected).into()
|
||||||
|
# }
|
||||||
|
# MatcherResult::NoMatch => {
|
||||||
|
# format!("isn't equal to {:?} the way I define it", self.expected).into()
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
pub fn eq_my_way<T: PartialEq + Debug + Copy>(expected: T) -> impl Matcher<T> {
|
||||||
|
MyEqMatcher { expected }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The new matcher can then be used in the assertion macros:
|
||||||
|
|
||||||
|
```
|
||||||
|
# use googletest::prelude::*;
|
||||||
|
# use googletest::{description::Description, matcher::{Matcher, MatcherBase, MatcherResult}};
|
||||||
|
# use std::fmt::Debug;
|
||||||
|
# #[derive(MatcherBase)]
|
||||||
|
# struct MyEqMatcher<T> {
|
||||||
|
# expected: T,
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# impl<T: PartialEq + Debug + Copy> Matcher<T> for MyEqMatcher<T> {
|
||||||
|
# fn matches(&self, actual: T) -> MatcherResult {
|
||||||
|
# if self.expected == actual {
|
||||||
|
# MatcherResult::Match
|
||||||
|
# } else {
|
||||||
|
# MatcherResult::NoMatch
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# fn describe(&self, matcher_result: MatcherResult) -> Description {
|
||||||
|
# match matcher_result {
|
||||||
|
# MatcherResult::Match => {
|
||||||
|
# format!("is equal to {:?} the way I define it", self.expected).into()
|
||||||
|
# }
|
||||||
|
# MatcherResult::NoMatch => {
|
||||||
|
# format!("isn't equal to {:?} the way I define it", self.expected).into()
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# pub fn eq_my_way<T: PartialEq + Debug + Copy>(expected: T) -> impl Matcher<T> {
|
||||||
|
# MyEqMatcher { expected }
|
||||||
|
# }
|
||||||
|
# /* The attribute macro would prevent the function from being compiled in a doctest.
|
||||||
|
#[gtest]
|
||||||
|
# */
|
||||||
|
fn should_be_equal_by_my_definition() {
|
||||||
|
# googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
|
||||||
|
expect_that!(10, eq_my_way(10));
|
||||||
|
# googletest::internal::test_outcome::TestOutcome::close_current_test_outcome::<&str>(Ok(()))
|
||||||
|
# .unwrap();
|
||||||
|
}
|
||||||
|
# should_be_equal_by_my_definition();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-fatal assertions
|
||||||
|
|
||||||
|
Using non-fatal assertions, a single test is able to log multiple assertion
|
||||||
|
failures. Any single assertion failure causes the test to be considered
|
||||||
|
having failed, but execution continues until the test completes or otherwise
|
||||||
|
aborts.
|
||||||
|
|
||||||
|
To make a non-fatal assertion, use the macro [`expect_that!`]. The test must
|
||||||
|
also be marked with [`gtest`] instead of the Rust-standard `#[test]`.
|
||||||
|
|
||||||
|
```no_run
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
#[gtest]
|
||||||
|
fn three_non_fatal_assertions() {
|
||||||
|
let value = 2;
|
||||||
|
expect_that!(value, eq(2)); // Passes; test still considered passing.
|
||||||
|
expect_that!(value, eq(3)); // Fails; logs failure and marks the test failed.
|
||||||
|
expect_that!(value, eq(4)); // A second failure, also logged.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be used in the same tests as `verify_that!`, in which case the test
|
||||||
|
function must also return [`Result<()>`]:
|
||||||
|
|
||||||
|
```no_run
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
# /* Make sure this also compiles as a doctest.
|
||||||
|
#[gtest]
|
||||||
|
# */
|
||||||
|
fn failing_non_fatal_assertion() -> Result<()> {
|
||||||
|
let value = 2;
|
||||||
|
expect_that!(value, eq(3)); // Just marks the test as having failed.
|
||||||
|
verify_that!(value, eq(2))?; // Passes, so does not abort the test.
|
||||||
|
Ok(()) // Because of the failing expect_that! call above, the
|
||||||
|
// test fails despite returning Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```no_run
|
||||||
|
use googletest::prelude::*;
|
||||||
|
|
||||||
|
#[gtest]
|
||||||
|
fn failing_fatal_assertion_after_non_fatal_assertion() -> Result<()> {
|
||||||
|
let value = 2;
|
||||||
|
expect_that!(value, eq(2)); // Passes; test still considered passing.
|
||||||
|
verify_that!(value, eq(3))?; // Fails and aborts the test.
|
||||||
|
expect_that!(value, eq(3)); // Never executes, since the test already aborted.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Predicate assertions
|
||||||
|
|
||||||
|
The macro [`verify_pred!`] provides predicate assertions analogous to
|
||||||
|
GoogleTest's `EXPECT_PRED` family of macros. Wrap an invocation of a
|
||||||
|
predicate in a `verify_pred!` invocation to turn that into a test assertion
|
||||||
|
which passes precisely when the predicate returns `true`:
|
||||||
|
|
||||||
|
```
|
||||||
|
# use googletest::prelude::*;
|
||||||
|
fn stuff_is_correct(x: i32, y: i32) -> bool {
|
||||||
|
x == y
|
||||||
|
}
|
||||||
|
|
||||||
|
# fn run_test() -> Result<()> {
|
||||||
|
let x = 3;
|
||||||
|
let y = 4;
|
||||||
|
verify_pred!(stuff_is_correct(x, y))?;
|
||||||
|
# Ok(())
|
||||||
|
# }
|
||||||
|
# run_test().unwrap_err();
|
||||||
|
```
|
||||||
|
|
||||||
|
The assertion failure message shows the arguments and the values to which
|
||||||
|
they evaluate:
|
||||||
|
|
||||||
|
```text
|
||||||
|
stuff_is_correct(x, y) was false with
|
||||||
|
x = 3,
|
||||||
|
y = 4
|
||||||
|
```
|
||||||
|
|
||||||
|
The `verify_pred!` invocation evaluates to a [`Result<()>`] just like
|
||||||
|
[`verify_that!`]. There is also a macro [`expect_pred!`] to make a non-fatal
|
||||||
|
predicaticate assertion.
|
||||||
|
|
||||||
|
## Unconditionally generating a test failure
|
||||||
|
|
||||||
|
The macro [`fail!`] unconditionally evaluates to a `Result` indicating a
|
||||||
|
test failure. It can be used analogously to [`verify_that!`] and
|
||||||
|
[`verify_pred!`] to cause a test to fail, with an optional formatted
|
||||||
|
message:
|
||||||
|
|
||||||
|
```
|
||||||
|
# use googletest::prelude::*;
|
||||||
|
# /* The attribute macro would prevent the function from being compiled in a doctest.
|
||||||
|
#[test]
|
||||||
|
# */
|
||||||
|
fn always_fails() -> Result<()> {
|
||||||
|
fail!("This test must fail with {}", "today")
|
||||||
|
}
|
||||||
|
# always_fails().unwrap_err();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conversion from `Result::Err` and `Option::None`
|
||||||
|
|
||||||
|
To simplify error management during a test arrangement, [`Result<T>`]
|
||||||
|
provides a few conversion utilities.
|
||||||
|
|
||||||
|
If your setup function returns `std::result::Result<T, E>` where `E: std::error::Error`,
|
||||||
|
the `std::result::Result<T, E>` can simply be handled with the `?` operator. If an `Err(e)`
|
||||||
|
is returned, the test will report a failure at the line where the `?` operator has been
|
||||||
|
applied (or the lowest caller without `#[track_caller]`).
|
||||||
|
|
||||||
|
```
|
||||||
|
# use googletest::prelude::*;
|
||||||
|
struct PngImage { h: i32, w: i32 /* ... */ }
|
||||||
|
impl PngImage {
|
||||||
|
fn new_from_file(file_name: &str) -> std::result::Result<Self, std::io::Error> {
|
||||||
|
Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"))
|
||||||
|
|
||||||
|
}
|
||||||
|
fn rotate(&mut self) { std::mem::swap(&mut self.h, &mut self.w);}
|
||||||
|
fn dimensions(&self) -> (i32, i32) { (self.h, self.w)}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_png_image_dimensions() -> googletest::Result<()> {
|
||||||
|
// Arrange
|
||||||
|
let mut png = PngImage::new_from_file("example.png")?;
|
||||||
|
verify_eq!(png.dimensions(), (128, 64))?;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
png.rotate();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect_eq!(png.dimensions(), (64, 128));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
# test_png_image_dimensions().unwrap_err();
|
||||||
|
```
|
||||||
|
|
||||||
|
If your setup function returns `Option<T>` or `std::result::Result<T, E>` where
|
||||||
|
`E: !std::error::Error`, then you can convert these types with `or_fail()`
|
||||||
|
from the `OrFail` extension trait.
|
||||||
|
|
||||||
|
```
|
||||||
|
# use googletest::prelude::*;
|
||||||
|
# struct PngImage;
|
||||||
|
# static PNG_BINARY: [u8;0] = [];
|
||||||
|
|
||||||
|
impl PngImage {
|
||||||
|
fn new_from_binary(bin: &[u8]) -> std::result::Result<Self, String> {
|
||||||
|
Err("Parsing failed".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# /* The attribute macro would prevent the function from being compiled in a doctest.
|
||||||
|
#[gtest]
|
||||||
|
# */
|
||||||
|
fn test_png_image_binary() -> googletest::Result<()> {
|
||||||
|
// Arrange
|
||||||
|
let png_image = PngImage::new_from_binary(&PNG_BINARY).or_fail()?;
|
||||||
|
/* ... */
|
||||||
|
# Ok(())
|
||||||
|
}
|
||||||
|
# test_png_image_binary().unwrap_err();
|
||||||
|
|
||||||
|
impl PngImage {
|
||||||
|
fn new_from_cache(key: u64) -> Option<Self> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# /* The attribute macro would prevent the function from being compiled in a doctest.
|
||||||
|
#[gtest]
|
||||||
|
# */
|
||||||
|
fn test_png_from_cache() -> googletest::Result<()> {
|
||||||
|
// Arrange
|
||||||
|
let png_image = PngImage::new_from_cache(123).or_fail()?;
|
||||||
|
/* ... */
|
||||||
|
# Ok(())
|
||||||
|
}
|
||||||
|
# test_png_from_cache().unwrap_err();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Multi-threaded tests
|
||||||
|
|
||||||
|
Due to internal use of thread-local storage, the `expect_*` family of macros
|
||||||
|
should only be invoked from the same thread as that running the test itself.
|
||||||
|
`verify_*` and `assert_*` macros on the other hand may be called from other
|
||||||
|
threads.
|
||||||
|
|
||||||
|
## Integrations with other crates
|
||||||
|
|
||||||
|
GoogleTest Rust includes integrations with the
|
||||||
|
[Proptest](https://crates.io/crates/proptest) crates to simplify turning
|
||||||
|
GoogleTest assertion failures into Proptest
|
||||||
|
[`TestCaseError`](https://docs.rs/proptest/latest/proptest/test_runner/enum.TestCaseError.html)
|
||||||
|
through the `?` operator.
|
||||||
|
|
||||||
|
[`and_log_failure()`]: GoogleTestSupport::and_log_failure
|
||||||
|
[`or_fail()`]: OrFail::or_fail
|
||||||
|
[`Matcher`]: matcher::Matcher
|
||||||
|
[`MatcherBase`]: matcher::MatcherBase
|
||||||
1918
third_party/crates/googletest-0.14.3/src/assertions.rs
vendored
Normal file
1918
third_party/crates/googletest-0.14.3/src/assertions.rs
vendored
Normal file
File diff suppressed because it is too large
Load Diff
406
third_party/crates/googletest-0.14.3/src/description.rs
vendored
Normal file
406
third_party/crates/googletest-0.14.3/src/description.rs
vendored
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
// Copyright 2023 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fmt::{Display, Formatter, Result},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::internal::description_renderer::{List, INDENTATION_SIZE};
|
||||||
|
|
||||||
|
/// A structured description, either of a (composed) matcher or of an
|
||||||
|
/// assertion failure.
|
||||||
|
///
|
||||||
|
/// One can compose blocks of text into a `Description`. Each one appears on a
|
||||||
|
/// new line. For example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # use googletest::description::Description;
|
||||||
|
/// let description = Description::new()
|
||||||
|
/// .text("A block")
|
||||||
|
/// .text("Another block");
|
||||||
|
/// verify_that!(description, displays_as(eq("A block\nAnother block")))
|
||||||
|
/// # .unwrap();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// One can embed nested descriptions into a `Description`. The resulting
|
||||||
|
/// nested description is then rendered with an additional level of
|
||||||
|
/// indentation. For example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # use googletest::description::Description;
|
||||||
|
/// let inner_description = Description::new()
|
||||||
|
/// .text("A block")
|
||||||
|
/// .text("Another block");
|
||||||
|
/// let outer_description = Description::new()
|
||||||
|
/// .text("Header")
|
||||||
|
/// .nested(inner_description);
|
||||||
|
/// verify_that!(outer_description, displays_as(eq("\
|
||||||
|
/// Header
|
||||||
|
/// A block
|
||||||
|
/// Another block")))
|
||||||
|
/// # .unwrap();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// One can also enumerate or bullet list the elements of a `Description`:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # use googletest::description::Description;
|
||||||
|
/// let description = Description::new()
|
||||||
|
/// .text("First item")
|
||||||
|
/// .text("Second item")
|
||||||
|
/// .bullet_list();
|
||||||
|
/// verify_that!(description, displays_as(eq("\
|
||||||
|
/// * First item
|
||||||
|
/// * Second item")))
|
||||||
|
/// # .unwrap();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// One can construct a `Description` from a [`String`] or a string slice, an
|
||||||
|
/// iterator thereof, or from an iterator over other `Description`s:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::description::Description;
|
||||||
|
/// let single_element_description: Description =
|
||||||
|
/// "A single block description".into();
|
||||||
|
/// let two_element_description: Description =
|
||||||
|
/// ["First item", "Second item"].into_iter().collect();
|
||||||
|
/// let two_element_description_from_strings: Description =
|
||||||
|
/// ["First item".to_string(), "Second item".to_string()].into_iter().collect();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// No newline is added after the last element during rendering. This makes it
|
||||||
|
/// easier to support single-line matcher descriptions and match explanations.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Description {
|
||||||
|
elements: List,
|
||||||
|
initial_indentation: usize,
|
||||||
|
is_conjunction: bool,
|
||||||
|
is_disjunction: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Description {
|
||||||
|
/// Returns a new empty [`Description`].
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a block of text to this instance.
|
||||||
|
///
|
||||||
|
/// The block is indented uniformly when this instance is rendered.
|
||||||
|
pub fn text(mut self, text: impl Into<Cow<'static, str>>) -> Self {
|
||||||
|
self.elements.push_literal(text.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends a nested [`Description`] to this instance.
|
||||||
|
///
|
||||||
|
/// The nested [`Description`] `inner` is indented uniformly at the next
|
||||||
|
/// level of indentation when this instance is rendered.
|
||||||
|
pub fn nested(mut self, inner: Description) -> Self {
|
||||||
|
self.elements.push_nested(inner.elements);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends all [`Description`] in the given sequence `inner` to this
|
||||||
|
/// instance.
|
||||||
|
///
|
||||||
|
/// Each element is treated as a nested [`Description`] in the sense of
|
||||||
|
/// [`Self::nested`].
|
||||||
|
pub fn collect(self, inner: impl IntoIterator<Item = Description>) -> Self {
|
||||||
|
inner.into_iter().fold(self, |outer, inner| outer.nested(inner))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indents the lines in elements of this description.
|
||||||
|
///
|
||||||
|
/// This operation will be performed lazily when [`self`] is displayed.
|
||||||
|
///
|
||||||
|
/// This will indent every line inside each element.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # use googletest::description::Description;
|
||||||
|
/// let description = std::iter::once("A B C\nD E F".to_string()).collect::<Description>();
|
||||||
|
/// verify_that!(description.indent(), displays_as(eq(" A B C\n D E F")))
|
||||||
|
/// # .unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn indent(self) -> Self {
|
||||||
|
Self { initial_indentation: INDENTATION_SIZE, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Instructs this instance to render its elements as a bullet list.
|
||||||
|
///
|
||||||
|
/// Each element (from either [`Description::text`] or
|
||||||
|
/// [`Description::nested`]) is rendered as a bullet point. If an element
|
||||||
|
/// contains multiple lines, the following lines are aligned with the first
|
||||||
|
/// one in the block.
|
||||||
|
///
|
||||||
|
/// For instance:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # use googletest::description::Description;
|
||||||
|
/// let description = Description::new()
|
||||||
|
/// .text("First line\nsecond line")
|
||||||
|
/// .bullet_list();
|
||||||
|
/// verify_that!(description, displays_as(eq("\
|
||||||
|
/// * First line
|
||||||
|
/// second line")))
|
||||||
|
/// # .unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn bullet_list(self) -> Self {
|
||||||
|
Self { elements: self.elements.bullet_list(), ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Instructs this instance to render its elements as an enumerated list.
|
||||||
|
///
|
||||||
|
/// Each element (from either [`Description::text`] or
|
||||||
|
/// [`Description::nested`]) is rendered with its zero-based index. If an
|
||||||
|
/// element contains multiple lines, the following lines are aligned with
|
||||||
|
/// the first one in the block.
|
||||||
|
///
|
||||||
|
/// For instance:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # use googletest::description::Description;
|
||||||
|
/// let description = Description::new()
|
||||||
|
/// .text("First line\nsecond line")
|
||||||
|
/// .enumerate();
|
||||||
|
/// verify_that!(description, displays_as(eq("\
|
||||||
|
/// 0. First line
|
||||||
|
/// second line")))
|
||||||
|
/// # .unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn enumerate(self) -> Self {
|
||||||
|
Self { elements: self.elements.enumerate(), ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of elements.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.elements.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the set of elements is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.elements.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn push_in_last_nested(mut self, inner: Description) -> Self {
|
||||||
|
self.elements.push_at_end(inner.elements);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn conjunction_description(self) -> Self {
|
||||||
|
Self { is_conjunction: true, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_conjunction_description(&self) -> bool {
|
||||||
|
self.is_conjunction
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn disjunction_description(self) -> Self {
|
||||||
|
Self { is_disjunction: true, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_disjunction_description(&self) -> bool {
|
||||||
|
self.is_disjunction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Description {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
|
self.elements.render(f, self.initial_indentation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ElementT: Into<Cow<'static, str>>> FromIterator<ElementT> for Description {
|
||||||
|
fn from_iter<T>(iter: T) -> Self
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = ElementT>,
|
||||||
|
{
|
||||||
|
Self { elements: iter.into_iter().map(ElementT::into).collect(), ..Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<Description> for Description {
|
||||||
|
fn from_iter<T>(iter: T) -> Self
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = Description>,
|
||||||
|
{
|
||||||
|
Self { elements: iter.into_iter().map(|s| s.elements).collect(), ..Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Cow<'static, str>>> From<T> for Description {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
let mut elements = List::default();
|
||||||
|
elements.push_literal(value.into());
|
||||||
|
Self { elements, ..Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Description;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::Result;
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_single_fragment() -> Result<()> {
|
||||||
|
let description: Description = "A B C".into();
|
||||||
|
verify_that!(description, displays_as(eq("A B C")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_two_fragments() -> Result<()> {
|
||||||
|
let description =
|
||||||
|
["A B C".to_string(), "D E F".to_string()].into_iter().collect::<Description>();
|
||||||
|
verify_that!(description, displays_as(eq("A B C\nD E F")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_description_is_indented() -> Result<()> {
|
||||||
|
let description = Description::new()
|
||||||
|
.text("Header")
|
||||||
|
.nested(["A B C".to_string()].into_iter().collect::<Description>());
|
||||||
|
verify_that!(description, displays_as(eq("Header\n A B C")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_description_indents_two_elements() -> Result<()> {
|
||||||
|
let description = Description::new().text("Header").nested(
|
||||||
|
["A B C".to_string(), "D E F".to_string()].into_iter().collect::<Description>(),
|
||||||
|
);
|
||||||
|
verify_that!(description, displays_as(eq("Header\n A B C\n D E F")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_description_indents_one_element_on_two_lines() -> Result<()> {
|
||||||
|
let description = Description::new().text("Header").nested("A B C\nD E F".into());
|
||||||
|
verify_that!(description, displays_as(eq("Header\n A B C\n D E F")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_fragment_renders_with_bullet_when_bullet_list_enabled() -> Result<()> {
|
||||||
|
let description = Description::new().text("A B C").bullet_list();
|
||||||
|
verify_that!(description, displays_as(eq("* A B C")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_nested_fragment_renders_with_bullet_when_bullet_list_enabled() -> Result<()> {
|
||||||
|
let description = Description::new().nested("A B C".into()).bullet_list();
|
||||||
|
verify_that!(description, displays_as(eq("* A B C")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_fragments_render_with_bullet_when_bullet_list_enabled() -> Result<()> {
|
||||||
|
let description = Description::new().text("A B C").text("D E F").bullet_list();
|
||||||
|
verify_that!(description, displays_as(eq("* A B C\n* D E F")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_nested_fragments_render_with_bullet_when_bullet_list_enabled() -> Result<()> {
|
||||||
|
let description =
|
||||||
|
Description::new().nested("A B C".into()).nested("D E F".into()).bullet_list();
|
||||||
|
verify_that!(description, displays_as(eq("* A B C\n* D E F")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_fragment_with_more_than_one_line_renders_with_one_bullet() -> Result<()> {
|
||||||
|
let description = Description::new().text("A B C\nD E F").bullet_list();
|
||||||
|
verify_that!(description, displays_as(eq("* A B C\n D E F")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_fragment_renders_with_enumeration_when_enumerate_enabled() -> Result<()> {
|
||||||
|
let description = Description::new().text("A B C").enumerate();
|
||||||
|
verify_that!(description, displays_as(eq("0. A B C")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_fragments_render_with_enumeration_when_enumerate_enabled() -> Result<()> {
|
||||||
|
let description = Description::new().text("A B C").text("D E F").enumerate();
|
||||||
|
verify_that!(description, displays_as(eq("0. A B C\n1. D E F")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_fragment_with_two_lines_renders_with_one_enumeration_label() -> Result<()> {
|
||||||
|
let description = Description::new().text("A B C\nD E F").enumerate();
|
||||||
|
verify_that!(description, displays_as(eq("0. A B C\n D E F")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_digit_enumeration_renders_with_correct_offset() -> Result<()> {
|
||||||
|
let description = ["A B C\nD E F"; 11]
|
||||||
|
.into_iter()
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect::<Description>()
|
||||||
|
.enumerate();
|
||||||
|
verify_that!(
|
||||||
|
description,
|
||||||
|
displays_as(eq(indoc!(
|
||||||
|
"
|
||||||
|
0. A B C
|
||||||
|
D E F
|
||||||
|
1. A B C
|
||||||
|
D E F
|
||||||
|
2. A B C
|
||||||
|
D E F
|
||||||
|
3. A B C
|
||||||
|
D E F
|
||||||
|
4. A B C
|
||||||
|
D E F
|
||||||
|
5. A B C
|
||||||
|
D E F
|
||||||
|
6. A B C
|
||||||
|
D E F
|
||||||
|
7. A B C
|
||||||
|
D E F
|
||||||
|
8. A B C
|
||||||
|
D E F
|
||||||
|
9. A B C
|
||||||
|
D E F
|
||||||
|
10. A B C
|
||||||
|
D E F"
|
||||||
|
)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_is_empty() -> Result<()> {
|
||||||
|
verify_that!(Description::new(), predicate(Description::is_empty))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_is_not_empty() -> Result<()> {
|
||||||
|
verify_that!(Description::new().text("something"), not(predicate(Description::is_empty)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_zero_length() -> Result<()> {
|
||||||
|
verify_that!(Description::new().len(), eq(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_one_length() -> Result<()> {
|
||||||
|
verify_that!(Description::new().text("something").len(), eq(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
317
third_party/crates/googletest-0.14.3/src/fixtures.rs
vendored
Normal file
317
third_party/crates/googletest-0.14.3/src/fixtures.rs
vendored
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
// Copyright 2022 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
any::{Any, TypeId},
|
||||||
|
collections::HashMap,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
sync::{Mutex, OnceLock},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Interface for structure to be set up and torn down as part of a test.
|
||||||
|
/// Types implementing `Fixture` can be passed as a reference argument to a
|
||||||
|
/// test function.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// struct MyFixture { ... }
|
||||||
|
///
|
||||||
|
/// impl Fixture for MyFixture { ... }
|
||||||
|
///
|
||||||
|
/// #[gtest]
|
||||||
|
/// fn test_with_fixture(my_fixture: &MyFixture) {...}
|
||||||
|
/// ```
|
||||||
|
pub trait Fixture: Sized {
|
||||||
|
/// Factory method of the `Fixture`.
|
||||||
|
///
|
||||||
|
/// This method is called by the test harness before the test case
|
||||||
|
/// that uses this fixture. If this method returns an `Err(...)`,
|
||||||
|
/// then the test case is not evaluated, automatically fails, and
|
||||||
|
/// only the fixtures previously set up are torn down.
|
||||||
|
fn set_up() -> crate::Result<Self>;
|
||||||
|
|
||||||
|
/// Clean up method for the fixture.
|
||||||
|
///
|
||||||
|
/// This method is called by the test harness after the test case
|
||||||
|
/// that uses this fixture. If the `Fixture` has been set up, the
|
||||||
|
/// test harness will call this method, even if the test case failed
|
||||||
|
/// or panicked.
|
||||||
|
fn tear_down(self) -> crate::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for structure to be set up before the test case.
|
||||||
|
/// Types implementing `ConsumableFixture` can be passed by value to
|
||||||
|
/// a test function.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// struct MyFixture { ... }
|
||||||
|
///
|
||||||
|
/// impl ConsumableFixture for MyFixture { ... }
|
||||||
|
///
|
||||||
|
/// #[gtest]
|
||||||
|
/// fn test_with_fixture(my_fixture: MyFixture) {...}
|
||||||
|
/// ```
|
||||||
|
pub trait ConsumableFixture: Sized {
|
||||||
|
/// Factory method of the `ConsumableFixture`.
|
||||||
|
///
|
||||||
|
/// This method is called by the test harness before the test case
|
||||||
|
/// that uses this fixture. If this method returns an `Err(...)`,
|
||||||
|
/// then the test case is not evaluated, automatically fails, and
|
||||||
|
/// only the fixtures previously set up are torn down.
|
||||||
|
fn set_up() -> crate::Result<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic adapter to implement `ConsumableFixture` on any type implementing
|
||||||
|
/// `Default`.
|
||||||
|
pub struct FixtureOf<T>(T);
|
||||||
|
|
||||||
|
impl<T: Default> ConsumableFixture for FixtureOf<T> {
|
||||||
|
fn set_up() -> crate::Result<Self> {
|
||||||
|
Ok(Self(T::default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for FixtureOf<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for FixtureOf<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interface for structure to be set up only once before all tests.
|
||||||
|
/// Types implementing `StaticFixture` can be passed as a double referenced
|
||||||
|
/// argument to a test function.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// struct MyFixture{ ... }
|
||||||
|
///
|
||||||
|
/// impl StaticFixture for MyFixture { ... }
|
||||||
|
///
|
||||||
|
/// #[gtest]
|
||||||
|
/// fn test_with_fixture(my_fixture: &&MyFixture){...}
|
||||||
|
/// ```
|
||||||
|
pub trait StaticFixture: Sized + Sync + Send {
|
||||||
|
/// Factory method of the `StaticFixture`.
|
||||||
|
///
|
||||||
|
/// This method is called by the test harness before the first test case
|
||||||
|
/// using this fixture. If this method returns an `Err(...)`, then every
|
||||||
|
/// test case using this fixture is not evaluated and automatically fails.
|
||||||
|
fn set_up_once() -> crate::Result<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: StaticFixture + 'static> Fixture for &'static F {
|
||||||
|
fn set_up() -> crate::Result<Self> {
|
||||||
|
static ONCE_FIXTURE_REPO: OnceLock<
|
||||||
|
Mutex<HashMap<TypeId, &'static (dyn Any + Sync + Send)>>,
|
||||||
|
> = OnceLock::new();
|
||||||
|
let mut map = ONCE_FIXTURE_REPO.get_or_init(|| Mutex::new(HashMap::new())).lock()?;
|
||||||
|
let any =
|
||||||
|
map.entry(TypeId::of::<F>()).or_insert_with(|| Box::leak(Box::new(F::set_up_once())));
|
||||||
|
match any.downcast_ref::<crate::Result<F>>() {
|
||||||
|
Some(Ok(fixture)) => Ok(fixture),
|
||||||
|
Some(Err(e)) => Err(e.clone()),
|
||||||
|
None => panic!("Downcast failed. This is a bug in GoogleTest Rust"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this is `&F` being torn down, not `F`.
|
||||||
|
fn tear_down(self) -> crate::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
|
use super::FixtureOf;
|
||||||
|
use super::StaticFixture;
|
||||||
|
use crate as googletest;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::test;
|
||||||
|
use crate::Result;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fixture_no_fixture() -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AlwaysSucceed;
|
||||||
|
|
||||||
|
impl Fixture for AlwaysSucceed {
|
||||||
|
fn set_up() -> crate::Result<Self> {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tear_down(self) -> crate::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fixture_one_fixture(_: &AlwaysSucceed) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fixture_three_fixtures(
|
||||||
|
_: &AlwaysSucceed,
|
||||||
|
_: &AlwaysSucceed,
|
||||||
|
_: &AlwaysSucceed,
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NotAFixture {
|
||||||
|
a_field: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NotAFixture {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { a_field: 33 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fixture_of_non_fixture(not_a_fixture: FixtureOf<NotAFixture>) -> Result<()> {
|
||||||
|
verify_that!(not_a_fixture.a_field, eq(33))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fixture_of_non_fixture_mut(mut not_a_fixture: FixtureOf<NotAFixture>) -> Result<()> {
|
||||||
|
not_a_fixture.a_field += 10;
|
||||||
|
verify_that!(not_a_fixture.a_field, eq(43))
|
||||||
|
}
|
||||||
|
struct PanickyFixture;
|
||||||
|
|
||||||
|
impl Fixture for PanickyFixture {
|
||||||
|
fn set_up() -> crate::Result<Self> {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tear_down(self) -> crate::Result<()> {
|
||||||
|
panic!("Whoooops");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "Whoooops")]
|
||||||
|
fn fixture_teardown_called_even_if_test_fail(_: &PanickyFixture) -> Result<()> {
|
||||||
|
Err(googletest::TestAssertionFailure::create("It must fail!".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AbortIfNotTornDownFixture {
|
||||||
|
has_been_torn_down: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fixture for AbortIfNotTornDownFixture {
|
||||||
|
fn set_up() -> crate::Result<Self> {
|
||||||
|
Ok(Self { has_been_torn_down: false })
|
||||||
|
}
|
||||||
|
fn tear_down(mut self) -> crate::Result<()> {
|
||||||
|
self.has_been_torn_down = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for AbortIfNotTornDownFixture {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.has_been_torn_down {
|
||||||
|
eprintln!("AbortIfNotTornDownFixture was not torn down");
|
||||||
|
std::process::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "simple_fail")]
|
||||||
|
fn fixture_torn_down_after_test_panics(f: &AbortIfNotTornDownFixture) {
|
||||||
|
expect_that!(f.has_been_torn_down, eq(false));
|
||||||
|
panic!("simple_fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OnlyOnce;
|
||||||
|
|
||||||
|
impl StaticFixture for OnlyOnce {
|
||||||
|
fn set_up_once() -> crate::Result<Self> {
|
||||||
|
static ONCE: Once = Once::new();
|
||||||
|
assert!(!ONCE.is_completed());
|
||||||
|
ONCE.call_once(|| {});
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn static_fixture_works(_: &&OnlyOnce) {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn static_fixture_same_static_fixture_twice(once: &&OnlyOnce, twice: &&OnlyOnce) {
|
||||||
|
// checks it points to the same memory address.
|
||||||
|
let once: *const OnlyOnce = *once;
|
||||||
|
let twice: *const OnlyOnce = *twice;
|
||||||
|
expect_eq!(once, twice);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AnotherStaticFixture;
|
||||||
|
|
||||||
|
impl StaticFixture for AnotherStaticFixture {
|
||||||
|
fn set_up_once() -> crate::Result<Self> {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn static_fixture_two_different_static_fixtures(_: &&OnlyOnce, _: &&AnotherStaticFixture) {}
|
||||||
|
|
||||||
|
struct FailingSetUp;
|
||||||
|
|
||||||
|
impl Fixture for FailingSetUp {
|
||||||
|
fn set_up() -> crate::Result<Self> {
|
||||||
|
Err(googletest::TestAssertionFailure::create("sad fixture".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tear_down(self) -> crate::Result<()> {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "See failure output above")]
|
||||||
|
fn failing_fixture_causes_test_failure(_: &FailingSetUp) {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FailingTearDown;
|
||||||
|
|
||||||
|
impl Fixture for FailingTearDown {
|
||||||
|
fn set_up() -> crate::Result<Self> {
|
||||||
|
Ok(Self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tear_down(self) -> crate::Result<()> {
|
||||||
|
Err(googletest::TestAssertionFailure::create("It must fail!".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "See failure output above")]
|
||||||
|
fn failing_teardown_causes_test_failure(_: &FailingTearDown) {}
|
||||||
|
}
|
||||||
67
third_party/crates/googletest-0.14.3/src/fmt.rs
vendored
Normal file
67
third_party/crates/googletest-0.14.3/src/fmt.rs
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2024 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
/// Functions for use only by the procedural macros in this module.
|
||||||
|
///
|
||||||
|
/// **For internal use only. API stablility is not guaranteed!**
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub mod internal {
|
||||||
|
use std::fmt::{Debug, Write};
|
||||||
|
|
||||||
|
/// Wrapper to allow for inherent-method specialization based on whether a
|
||||||
|
/// type implements `Debug`.
|
||||||
|
pub struct FormatWrapper<'a, T: ?Sized>(pub &'a T);
|
||||||
|
|
||||||
|
/// Default implementation to render values that implement `Debug`.
|
||||||
|
///
|
||||||
|
/// Used for autoref specialization to conditionally
|
||||||
|
/// render only values that implement `Debug`. See also
|
||||||
|
/// [`FormatNonDebugFallback`].
|
||||||
|
impl<T: Debug + ?Sized> FormatWrapper<'_, T> {
|
||||||
|
#[track_caller]
|
||||||
|
pub fn __googletest_write_expr_value(&self, output: &mut dyn Write, expr_label: &str) {
|
||||||
|
write!(output, "\n {} = {:?},", expr_label, self.0)
|
||||||
|
.expect("Formatting to String should never fail");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fallback implementation for rendering values for non-`Debug` types..
|
||||||
|
///
|
||||||
|
/// Used for inherent-method specialization to conditionally render only
|
||||||
|
/// values that implement `Debug`. See also the specialized inherent impl on
|
||||||
|
/// [`FormatWrapper`] above.
|
||||||
|
pub trait FormatNonDebugFallback {
|
||||||
|
fn __googletest_write_expr_value(&self, output: &mut dyn Write, expr_label: &str);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> FormatNonDebugFallback for FormatWrapper<'_, T> {
|
||||||
|
#[track_caller]
|
||||||
|
fn __googletest_write_expr_value(&self, output: &mut dyn Write, expr_label: &str) {
|
||||||
|
write!(output, "\n {expr_label} does not implement Debug,")
|
||||||
|
.expect("Formatting to String should never fail");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! __googletest__write_expr_value(
|
||||||
|
($output:expr, $expr_str:expr, $value:expr $(,)?) => {
|
||||||
|
{
|
||||||
|
use $crate::fmt::internal::FormatNonDebugFallback as _;
|
||||||
|
$crate::fmt::internal::FormatWrapper(&$value)
|
||||||
|
.__googletest_write_expr_value(&mut $output, $expr_str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
pub use __googletest__write_expr_value;
|
||||||
|
}
|
||||||
626
third_party/crates/googletest-0.14.3/src/internal/description_renderer.rs
vendored
Normal file
626
third_party/crates/googletest-0.14.3/src/internal/description_renderer.rs
vendored
Normal file
@@ -0,0 +1,626 @@
|
|||||||
|
// Copyright 2023 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fmt::{Result, Write},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Number of space used to indent lines when no alignement is required.
|
||||||
|
pub(crate) const INDENTATION_SIZE: usize = 2;
|
||||||
|
|
||||||
|
/// A list of [`Block`] possibly rendered with a [`Decoration`].
|
||||||
|
///
|
||||||
|
/// This is the top-level renderable component, corresponding to the description
|
||||||
|
/// or match explanation of a single matcher.
|
||||||
|
///
|
||||||
|
/// The constituent [`Block`] of a `List` can be decorated with either bullets
|
||||||
|
/// (`* `) or enumeration (`0. `, `1. `, ...). This is controlled via the
|
||||||
|
/// methods [`List::bullet_list`] and [`List::enumerate`]. By default, there is
|
||||||
|
/// no decoration.
|
||||||
|
///
|
||||||
|
/// A `List` can be constructed as follows:
|
||||||
|
///
|
||||||
|
/// * [`Default::default()`] constructs an empty `List`.
|
||||||
|
/// * [`Iterator::collect()`] on an [`Iterator`] of [`Block`].
|
||||||
|
/// * [`Iterator::collect()`] on an [`Iterator`] of `String`, which produces a
|
||||||
|
/// [`Block::Literal`] for each `String`.
|
||||||
|
/// * [`Iterator::collect()`] on an [`Iterator`] of `List`, which produces a
|
||||||
|
/// [`Block::Nested`] for each `List`.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct List(Vec<Block>, Decoration);
|
||||||
|
|
||||||
|
impl List {
|
||||||
|
/// Render this instance using the formatter `f`.
|
||||||
|
///
|
||||||
|
/// Indent each line of output by `indentation` spaces.
|
||||||
|
pub(crate) fn render(&self, f: &mut dyn Write, indentation: usize) -> Result {
|
||||||
|
self.render_with_prefix(f, indentation, "".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append a new [`Block`] containing `literal`.
|
||||||
|
///
|
||||||
|
/// The input `literal` is split into lines so that each line will be
|
||||||
|
/// indented correctly.
|
||||||
|
pub(crate) fn push_literal(&mut self, literal: Cow<'static, str>) {
|
||||||
|
self.0.push(literal.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append a new [`Block`] containing `inner` as a nested [`List`].
|
||||||
|
pub(crate) fn push_nested(&mut self, inner: List) {
|
||||||
|
self.0.push(Block::Nested(inner));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render each [`Block`] of this instance preceded with a bullet "* ".
|
||||||
|
pub(crate) fn bullet_list(self) -> Self {
|
||||||
|
Self(self.0, Decoration::Bullet)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render each [`Block`] of this instance preceded with its 0-based index.
|
||||||
|
pub(crate) fn enumerate(self) -> Self {
|
||||||
|
Self(self.0, Decoration::Enumerate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of [`Block`] in this instance.
|
||||||
|
pub(crate) fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if there are no [`Block`] in this instance, `false`
|
||||||
|
/// otherwise.
|
||||||
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append a new [`List`] in the last element which must be a
|
||||||
|
/// [`Block::Nested`]. Panic if `self` is empty or the last element is
|
||||||
|
/// not [`Block::Nested`].
|
||||||
|
pub(crate) fn push_at_end(&mut self, list: List) {
|
||||||
|
if let Some(Block::Nested(self_list)) = self.0.last_mut() {
|
||||||
|
self_list.push_nested(list);
|
||||||
|
} else {
|
||||||
|
panic!("pushing elements at the end of {self:#?} which last element is not Nested")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_with_prefix(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn Write,
|
||||||
|
indentation: usize,
|
||||||
|
prefix: Cow<'static, str>,
|
||||||
|
) -> Result {
|
||||||
|
if self.0.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let enumeration_padding = self.enumeration_padding();
|
||||||
|
|
||||||
|
self.0[0].render(
|
||||||
|
f,
|
||||||
|
indentation,
|
||||||
|
self.full_prefix(0, enumeration_padding, &prefix).into(),
|
||||||
|
)?;
|
||||||
|
for (index, block) in self.0[1..].iter().enumerate() {
|
||||||
|
writeln!(f)?;
|
||||||
|
block.render(
|
||||||
|
f,
|
||||||
|
indentation + prefix.len(),
|
||||||
|
self.prefix(index + 1, enumeration_padding),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_prefix(&self, index: usize, enumeration_padding: usize, prior_prefix: &str) -> String {
|
||||||
|
format!("{prior_prefix}{}", self.prefix(index, enumeration_padding))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prefix(&self, index: usize, enumeration_padding: usize) -> Cow<'static, str> {
|
||||||
|
match self.1 {
|
||||||
|
Decoration::None => "".into(),
|
||||||
|
Decoration::Bullet => "* ".into(),
|
||||||
|
Decoration::Enumerate => format!("{index:>enumeration_padding$}. ").into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enumeration_padding(&self) -> usize {
|
||||||
|
match self.1 {
|
||||||
|
Decoration::None => 0,
|
||||||
|
Decoration::Bullet => 0,
|
||||||
|
Decoration::Enumerate => {
|
||||||
|
if self.0.len() > 1 {
|
||||||
|
((self.0.len() - 1) as f64).log10().floor() as usize + 1
|
||||||
|
} else {
|
||||||
|
// Avoid negative logarithm when there is only 0 or 1 element.
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<Block> for List {
|
||||||
|
fn from_iter<T>(iter: T) -> Self
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = Block>,
|
||||||
|
{
|
||||||
|
Self(iter.into_iter().collect(), Decoration::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ElementT: Into<Cow<'static, str>>> FromIterator<ElementT> for List {
|
||||||
|
fn from_iter<T>(iter: T) -> Self
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = ElementT>,
|
||||||
|
{
|
||||||
|
Self(iter.into_iter().map(|b| b.into().into()).collect(), Decoration::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<List> for List {
|
||||||
|
fn from_iter<T>(iter: T) -> Self
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = List>,
|
||||||
|
{
|
||||||
|
Self(iter.into_iter().map(Block::nested).collect(), Decoration::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sequence of [`Fragment`] or a nested [`List`].
|
||||||
|
///
|
||||||
|
/// This may be rendered with a prefix specified by the [`Decoration`] of the
|
||||||
|
/// containing [`List`]. In this case, all lines are indented to align with the
|
||||||
|
/// first character of the first line of the block.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Block {
|
||||||
|
/// A block of text.
|
||||||
|
///
|
||||||
|
/// Each constituent [`Fragment`] contains one line of text. The lines are
|
||||||
|
/// indented uniformly to the current indentation of this block when
|
||||||
|
/// rendered.
|
||||||
|
Literal(Vec<Fragment>),
|
||||||
|
|
||||||
|
/// A nested [`List`].
|
||||||
|
///
|
||||||
|
/// The [`List`] is rendered recursively at the next level of indentation.
|
||||||
|
Nested(List),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Block {
|
||||||
|
fn nested(inner: List) -> Self {
|
||||||
|
Self::Nested(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self, f: &mut dyn Write, indentation: usize, prefix: Cow<'static, str>) -> Result {
|
||||||
|
match self {
|
||||||
|
Self::Literal(fragments) => {
|
||||||
|
if fragments.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "{:indentation$}{prefix}", "")?;
|
||||||
|
fragments[0].render(f)?;
|
||||||
|
let block_indentation = indentation + prefix.as_ref().len();
|
||||||
|
for fragment in &fragments[1..] {
|
||||||
|
writeln!(f)?;
|
||||||
|
write!(f, "{:block_indentation$}", "")?;
|
||||||
|
fragment.render(f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Self::Nested(inner) => inner.render_with_prefix(
|
||||||
|
f,
|
||||||
|
indentation + INDENTATION_SIZE.saturating_sub(prefix.len()),
|
||||||
|
prefix,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Block {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Block::Literal(value.lines().map(|v| Fragment(v.to_string().into())).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for Block {
|
||||||
|
fn from(value: &'static str) -> Self {
|
||||||
|
Block::Literal(value.lines().map(|v| Fragment(v.into())).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Cow<'static, str>> for Block {
|
||||||
|
fn from(value: Cow<'static, str>) -> Self {
|
||||||
|
match value {
|
||||||
|
Cow::Borrowed(value) => value.into(),
|
||||||
|
Cow::Owned(value) => value.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A string representing one line of a description or match explanation.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Fragment(Cow<'static, str>);
|
||||||
|
|
||||||
|
impl Fragment {
|
||||||
|
fn render(&self, f: &mut dyn Write) -> Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The decoration which appears on [`Block`] of a [`List`] when rendered.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
enum Decoration {
|
||||||
|
/// No decoration on each [`Block`]. The default.
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// Each [`Block`] is preceded by a bullet (`* `).
|
||||||
|
Bullet,
|
||||||
|
|
||||||
|
/// Each [`Block`] is preceded by its index in the [`List`] (`0. `, `1. `,
|
||||||
|
/// ...).
|
||||||
|
Enumerate,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{Block, Fragment, List};
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::Result;
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_fragment() -> Result<()> {
|
||||||
|
let fragment = Fragment("A fragment".into());
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
fragment.render(&mut result)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("A fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_empty_block() -> Result<()> {
|
||||||
|
let block = Block::Literal(vec![]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
block.render(&mut result, 0, "".into())?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_block_with_one_fragment() -> Result<()> {
|
||||||
|
let block: Block = "A fragment".into();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
block.render(&mut result, 0, "".into())?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("A fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_block_with_two_fragments() -> Result<()> {
|
||||||
|
let block: Block = "A fragment\nAnother fragment".into();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
block.render(&mut result, 0, "".into())?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("A fragment\nAnother fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_indented_block() -> Result<()> {
|
||||||
|
let block: Block = "A fragment\nAnother fragment".into();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
block.render(&mut result, 2, "".into())?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(" A fragment\n Another fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_block_with_prefix() -> Result<()> {
|
||||||
|
let block: Block = "A fragment\nAnother fragment".into();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
block.render(&mut result, 0, "* ".into())?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("* A fragment\n Another fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_indented_block_with_prefix() -> Result<()> {
|
||||||
|
let block: Block = "A fragment\nAnother fragment".into();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
block.render(&mut result, 2, "* ".into())?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(" * A fragment\n Another fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_empty_list() -> Result<()> {
|
||||||
|
let list = list(vec![]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_plain_list_with_one_block() -> Result<()> {
|
||||||
|
let list = list(vec!["A fragment".into()]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("A fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_plain_list_with_two_blocks() -> Result<()> {
|
||||||
|
let list = list(vec!["A fragment".into(), "A fragment in a second block".into()]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("A fragment\nA fragment in a second block"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_plain_list_with_one_block_with_two_fragments() -> Result<()> {
|
||||||
|
let list = list(vec!["A fragment\nA second fragment".into()]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("A fragment\nA second fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_nested_plain_list_with_one_block() -> Result<()> {
|
||||||
|
let list = list(vec![Block::nested(list(vec!["A fragment".into()]))]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(" A fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_nested_plain_list_with_two_blocks() -> Result<()> {
|
||||||
|
let list = list(vec![Block::nested(list(vec![
|
||||||
|
"A fragment".into(),
|
||||||
|
"A fragment in a second block".into(),
|
||||||
|
]))]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(" A fragment\n A fragment in a second block"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_nested_plain_list_with_one_block_with_two_fragments() -> Result<()> {
|
||||||
|
let list = list(vec![Block::nested(list(vec!["A fragment\nA second fragment".into()]))]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(" A fragment\n A second fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_bulleted_list_with_one_block() -> Result<()> {
|
||||||
|
let list = list(vec!["A fragment".into()]).bullet_list();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("* A fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_bulleted_list_with_two_blocks() -> Result<()> {
|
||||||
|
let list =
|
||||||
|
list(vec!["A fragment".into(), "A fragment in a second block".into()]).bullet_list();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("* A fragment\n* A fragment in a second block"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_bulleted_list_with_one_block_with_two_fragments() -> Result<()> {
|
||||||
|
let list = list(vec!["A fragment\nA second fragment".into()]).bullet_list();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("* A fragment\n A second fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_nested_bulleted_list_with_one_block() -> Result<()> {
|
||||||
|
let list = list(vec![Block::nested(list(vec!["A fragment".into()]).bullet_list())]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(" * A fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_nested_bulleted_list_with_two_blocks() -> Result<()> {
|
||||||
|
let list = list(vec![Block::nested(
|
||||||
|
list(vec!["A fragment".into(), "A fragment in a second block".into()]).bullet_list(),
|
||||||
|
)]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(" * A fragment\n * A fragment in a second block"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_nested_bulleted_list_with_one_block_with_two_fragments() -> Result<()> {
|
||||||
|
let list = list(vec![Block::nested(
|
||||||
|
list(vec!["A fragment\nA second fragment".into()]).bullet_list(),
|
||||||
|
)]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(" * A fragment\n A second fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_enumerated_list_with_one_block() -> Result<()> {
|
||||||
|
let list = list(vec!["A fragment".into()]).enumerate();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("0. A fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_enumerated_list_with_two_blocks() -> Result<()> {
|
||||||
|
let list =
|
||||||
|
list(vec!["A fragment".into(), "A fragment in a second block".into()]).enumerate();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("0. A fragment\n1. A fragment in a second block"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_enumerated_list_with_one_block_with_two_fragments() -> Result<()> {
|
||||||
|
let list = list(vec!["A fragment\nA second fragment".into()]).enumerate();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("0. A fragment\n A second fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn aligns_renders_enumerated_list_with_more_than_ten_blocks() -> Result<()> {
|
||||||
|
let list =
|
||||||
|
(0..11).map(|i| Block::from(format!("Fragment {i}"))).collect::<List>().enumerate();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(
|
||||||
|
result,
|
||||||
|
eq(indoc! {"
|
||||||
|
0. Fragment 0
|
||||||
|
1. Fragment 1
|
||||||
|
2. Fragment 2
|
||||||
|
3. Fragment 3
|
||||||
|
4. Fragment 4
|
||||||
|
5. Fragment 5
|
||||||
|
6. Fragment 6
|
||||||
|
7. Fragment 7
|
||||||
|
8. Fragment 8
|
||||||
|
9. Fragment 9
|
||||||
|
10. Fragment 10"})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_fragment_plus_nested_plain_list_with_one_block() -> Result<()> {
|
||||||
|
let list =
|
||||||
|
list(vec!["A fragment".into(), Block::nested(list(vec!["Another fragment".into()]))]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("A fragment\n Another fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_double_nested_plain_list_with_one_block() -> Result<()> {
|
||||||
|
let list =
|
||||||
|
list(vec![Block::nested(list(vec![Block::nested(list(vec!["A fragment".into()]))]))]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq(" A fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_headers_plus_double_nested_plain_list() -> Result<()> {
|
||||||
|
let list = list(vec![
|
||||||
|
"First header".into(),
|
||||||
|
Block::nested(list(vec![
|
||||||
|
"Second header".into(),
|
||||||
|
Block::nested(list(vec!["A fragment".into()])),
|
||||||
|
])),
|
||||||
|
]);
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("First header\n Second header\n A fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_double_nested_bulleted_list() -> Result<()> {
|
||||||
|
let list =
|
||||||
|
list(vec![Block::nested(list(vec!["A fragment".into()]).bullet_list())]).bullet_list();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("* * A fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_nested_enumeration_with_two_blocks_inside_bulleted_list() -> Result<()> {
|
||||||
|
let list =
|
||||||
|
list(vec![Block::nested(list(vec!["Block 1".into(), "Block 2".into()]).enumerate())])
|
||||||
|
.bullet_list();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("* 0. Block 1\n 1. Block 2"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn renders_nested_enumeration_with_block_with_two_fragments_inside_bulleted_list() -> Result<()>
|
||||||
|
{
|
||||||
|
let list = list(vec![Block::nested(
|
||||||
|
list(vec!["A fragment\nAnother fragment".into()]).enumerate(),
|
||||||
|
)])
|
||||||
|
.bullet_list();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
list.render(&mut result, 0)?;
|
||||||
|
|
||||||
|
verify_that!(result, eq("* 0. A fragment\n Another fragment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(blocks: Vec<Block>) -> List {
|
||||||
|
List(blocks, super::Decoration::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
312
third_party/crates/googletest-0.14.3/src/internal/glob.rs
vendored
Normal file
312
third_party/crates/googletest-0.14.3/src/internal/glob.rs
vendored
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
//! Pattern matches using glob wildcards `*` and `?` with the
|
||||||
|
//! linear-time algorithm <https://research.swtch.com/glob>.
|
||||||
|
|
||||||
|
use std::iter::Peekable;
|
||||||
|
use std::str::Chars;
|
||||||
|
|
||||||
|
pub struct Pattern(String);
|
||||||
|
|
||||||
|
impl Pattern {
|
||||||
|
/// Creates a new pattern matcher. Each pattern consists of
|
||||||
|
/// regular characters, single-character wildcards `'?'`, and
|
||||||
|
/// multi-character wildcards `'*'`.
|
||||||
|
pub fn new(pattern: String) -> Self {
|
||||||
|
Self(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if and only if the wildcard pattern matches the
|
||||||
|
/// string.
|
||||||
|
pub fn matches(&self, string: &str) -> bool {
|
||||||
|
let processor = Processor {
|
||||||
|
pattern: self.0.chars().peekable(),
|
||||||
|
string: string.chars().peekable(),
|
||||||
|
restart: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
processor.process()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeekableChars<'a> = Peekable<Chars<'a>>;
|
||||||
|
|
||||||
|
/// Represents the state we need to restart search from a star wildcard (`*`).
|
||||||
|
struct Restart<'a> {
|
||||||
|
pattern_next: PeekableChars<'a>,
|
||||||
|
string_next: PeekableChars<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The runtime state for glob matching.
|
||||||
|
struct Processor<'a> {
|
||||||
|
pattern: PeekableChars<'a>,
|
||||||
|
string: PeekableChars<'a>,
|
||||||
|
restart: Option<Restart<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents what to do after any step through the processor.
|
||||||
|
enum StepOutcome {
|
||||||
|
Proceed,
|
||||||
|
TryRestart,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Processor<'_> {
|
||||||
|
/// Runs the pattern matching until we find an unrecoverable
|
||||||
|
/// mismatch, or the input is consumed.
|
||||||
|
fn process(mut self) -> bool {
|
||||||
|
while self.is_unfinished() {
|
||||||
|
let outcome = self.step();
|
||||||
|
if let StepOutcome::TryRestart = outcome {
|
||||||
|
let restarted = self.try_restart();
|
||||||
|
if !restarted {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_unfinished(&mut self) -> bool {
|
||||||
|
self.pattern.peek().is_some() || self.string.peek().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a single step forward, and returns whether to proceed or try to
|
||||||
|
/// restart.
|
||||||
|
fn step(&mut self) -> StepOutcome {
|
||||||
|
match self.pattern.peek() {
|
||||||
|
Some('?') => self.step_question_wildcard(),
|
||||||
|
Some('*') => self.step_star_wildcard(),
|
||||||
|
Some(pattern_ch) => {
|
||||||
|
let pattern_ch = *pattern_ch;
|
||||||
|
self.step_ordinary_character(pattern_ch)
|
||||||
|
}
|
||||||
|
None => StepOutcome::TryRestart,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match any single character.
|
||||||
|
fn step_question_wildcard(&mut self) -> StepOutcome {
|
||||||
|
if self.string.peek().is_some() {
|
||||||
|
_ = self.pattern.next();
|
||||||
|
_ = self.string.next();
|
||||||
|
StepOutcome::Proceed
|
||||||
|
} else {
|
||||||
|
StepOutcome::TryRestart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match zero or more characters. Start by skipping over the
|
||||||
|
/// wildcard and matching zero characters from string. If that
|
||||||
|
/// fails, restart and match one more character than the last
|
||||||
|
/// attempt.
|
||||||
|
fn step_star_wildcard(&mut self) -> StepOutcome {
|
||||||
|
self.restart = if self.string.peek().is_none() {
|
||||||
|
// Subtle: if the string is already exhausted, we mark
|
||||||
|
// that we can't restart.
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let pattern_next = self.pattern.clone();
|
||||||
|
let mut string_next = self.string.clone();
|
||||||
|
string_next.next();
|
||||||
|
Some(Restart { pattern_next, string_next })
|
||||||
|
};
|
||||||
|
|
||||||
|
_ = self.pattern.next();
|
||||||
|
StepOutcome::Proceed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match an ordinary (non-wildcard) character.
|
||||||
|
fn step_ordinary_character(&mut self, pattern_ch: char) -> StepOutcome {
|
||||||
|
if self.string.peek() == Some(&pattern_ch) {
|
||||||
|
_ = self.pattern.next();
|
||||||
|
_ = self.string.next();
|
||||||
|
StepOutcome::Proceed
|
||||||
|
} else {
|
||||||
|
StepOutcome::TryRestart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to restart from failing to match a character. If true, the
|
||||||
|
/// matching can restart.
|
||||||
|
fn try_restart(&mut self) -> bool {
|
||||||
|
if let Some(Restart { pattern_next, string_next }) = &self.restart {
|
||||||
|
self.pattern = pattern_next.clone();
|
||||||
|
self.string = string_next.clone();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if `s` contains glob wildcards.
|
||||||
|
pub fn is_glob_pattern(s: &str) -> bool {
|
||||||
|
s.contains(['?', '*'])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_character_match() -> Result<()> {
|
||||||
|
verify_that!(Pattern::new("a".into()).matches("a"), is_true())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_character_mismatch() -> Result<()> {
|
||||||
|
verify_that!(Pattern::new("b".into()).matches("a"), is_false())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_word_mismatch() -> Result<()> {
|
||||||
|
verify_that!(Pattern::new("judgement".into()).matches("judgment"), is_false())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn question_match() -> Result<()> {
|
||||||
|
verify_that!(Pattern::new("?".into()).matches("a"), is_true())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_word_question_match() -> Result<()> {
|
||||||
|
let pattern = Pattern::new("judg?ment".into());
|
||||||
|
verify_that!(pattern.matches("judgment"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("judgement"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("judge ment"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn question_mismatch() -> Result<()> {
|
||||||
|
let pattern = Pattern::new("?".into());
|
||||||
|
verify_that!(pattern.matches(""), is_false())?;
|
||||||
|
verify_that!(pattern.matches("aa"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glob_on_empty() -> Result<()> {
|
||||||
|
verify_that!(Pattern::new("*".into()).matches(""), is_true())?;
|
||||||
|
verify_that!(Pattern::new("**".into()).matches(""), is_true())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glob_prefix() -> Result<()> {
|
||||||
|
let pattern = Pattern::new("*a".into());
|
||||||
|
verify_that!(pattern.matches(""), is_false())?;
|
||||||
|
verify_that!(pattern.matches("a"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("ba"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("bba"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("bbab"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glob_within() -> Result<()> {
|
||||||
|
let pattern = Pattern::new("b*a".into());
|
||||||
|
verify_that!(pattern.matches(""), is_false())?;
|
||||||
|
verify_that!(pattern.matches("b"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("bb"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("ba"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("bbbba"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("baa"), is_true())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glob_suffix() -> Result<()> {
|
||||||
|
let pattern = Pattern::new("ba*".into());
|
||||||
|
verify_that!(pattern.matches(""), is_false())?;
|
||||||
|
verify_that!(pattern.matches("b"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("bb"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("ba"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("baa"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("bab"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("bba"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("bbbba"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn redundant_stars() -> Result<()> {
|
||||||
|
let pattern = Pattern::new("**a".into());
|
||||||
|
verify_that!(pattern.matches(""), is_false())?;
|
||||||
|
verify_that!(pattern.matches("a"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("ba"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("bba"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("bbab"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("bbaba"), is_true())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn star_question_star_case() -> Result<()> {
|
||||||
|
let pattern = Pattern::new("*?*".into());
|
||||||
|
verify_that!(pattern.matches(""), is_false())?;
|
||||||
|
verify_that!(pattern.matches("a"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("aa"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("aaa"), is_true())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn another_case_finding_two_separated_a() -> Result<()> {
|
||||||
|
let pattern = Pattern::new("*a?a*".into());
|
||||||
|
verify_that!(pattern.matches(""), is_false())?;
|
||||||
|
verify_that!(pattern.matches("a"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("aa"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("aaa"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("aba"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("baba"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("abab"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("babab"), is_true())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn banana() -> Result<()> {
|
||||||
|
let pattern = Pattern::new("b?n???".into());
|
||||||
|
verify_that!(pattern.matches("banana"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("binary"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("bundle"), is_true())?;
|
||||||
|
verify_that!(pattern.matches("bindir"), is_true())?;
|
||||||
|
|
||||||
|
verify_that!(pattern.matches("bananas"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("bucket"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("budget"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("bazzar"), is_false())?;
|
||||||
|
verify_that!(pattern.matches("burger"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn glob_word() -> Result<()> {
|
||||||
|
let word = Pattern::new("*word*".into());
|
||||||
|
verify_that!(word.matches("bird"), is_false())?;
|
||||||
|
verify_that!(word.matches("This is a wordy sentence"), is_true())?;
|
||||||
|
verify_that!(word.matches("word soup"), is_true())?;
|
||||||
|
verify_that!(word.matches("bird is the word"), is_true())?;
|
||||||
|
verify_that!(word.matches("word"), is_true())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn degenerate_glob() -> Result<()> {
|
||||||
|
verify_that!(
|
||||||
|
Pattern::new("************************************************.*".into())
|
||||||
|
.matches("this is a test.com"),
|
||||||
|
is_true()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn degenerate_glob_2_mismatch() -> Result<()> {
|
||||||
|
// The first example from https://research.swtch.com/glob, with N=1000.
|
||||||
|
const N: usize = 1000;
|
||||||
|
let long_pattern = ["a*"; N].into_iter().collect::<String>() + "b";
|
||||||
|
let long_string = ["a"; N].into_iter().collect::<String>();
|
||||||
|
verify_that!(Pattern::new(long_pattern).matches(&long_string), is_false())
|
||||||
|
}
|
||||||
|
}
|
||||||
23
third_party/crates/googletest-0.14.3/src/internal/mod.rs
vendored
Normal file
23
third_party/crates/googletest-0.14.3/src/internal/mod.rs
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2022 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#![doc(hidden)]
|
||||||
|
|
||||||
|
pub(crate) mod description_renderer;
|
||||||
|
pub mod glob;
|
||||||
|
pub mod scoped_trace;
|
||||||
|
pub mod test_data;
|
||||||
|
pub mod test_filter;
|
||||||
|
pub mod test_outcome;
|
||||||
|
pub mod test_sharding;
|
||||||
126
third_party/crates/googletest-0.14.3/src/internal/scoped_trace.rs
vendored
Normal file
126
third_party/crates/googletest-0.14.3/src/internal/scoped_trace.rs
vendored
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2026 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
static NEXT_TRACE_ID: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
/// Information about a scoped trace.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TraceInfo {
|
||||||
|
pub id: usize,
|
||||||
|
pub file: &'static str,
|
||||||
|
pub line: u32,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static TRACE_STACK: RefCell<Vec<TraceInfo>> = const { RefCell::new(Vec::new()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RAII guard to manage the push and pop of trace information.
|
||||||
|
///
|
||||||
|
/// This struct is `!Send` and `!Sync` to prevent it from being held across
|
||||||
|
/// `.await` points in async tests, which would cause incorrect trace tracking
|
||||||
|
/// if the task moves between threads.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct ScopedTraceGuard {
|
||||||
|
id: usize,
|
||||||
|
_phantom: std::marker::PhantomData<*mut ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScopedTraceGuard {
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new(message: String) -> Self {
|
||||||
|
let caller = std::panic::Location::caller();
|
||||||
|
let id = NEXT_TRACE_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
|
TRACE_STACK.with(|stack| {
|
||||||
|
// Use try_borrow_mut to avoid double panic if called during unwinding.
|
||||||
|
if let Ok(mut s) = stack.try_borrow_mut() {
|
||||||
|
s.push(TraceInfo { id, file: caller.file(), line: caller.line(), message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Self { id, _phantom: std::marker::PhantomData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ScopedTraceGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
TRACE_STACK.with(|stack| {
|
||||||
|
// Use try_borrow_mut to avoid double panic if called during unwinding.
|
||||||
|
if let Ok(mut s) = stack.try_borrow_mut() {
|
||||||
|
if let Some(pos) = s.iter().rposition(|t| t.id == self.id) {
|
||||||
|
s.remove(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a clone of the current thread's trace stack.
|
||||||
|
pub fn get_scoped_traces() -> Vec<TraceInfo> {
|
||||||
|
TRACE_STACK.with(|stack| stack.try_borrow().map(|s| s.clone()).unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test-only state and helpers, hidden from production API.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod test_helpers {
|
||||||
|
use super::*;
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub static CAPTURED_TRACES_IN_HOOK: RefCell<Vec<TraceInfo>> = const { RefCell::new(Vec::new()) };
|
||||||
|
pub static USE_CAPTURE_HOOK: Cell<bool> = const { Cell::new(false) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_capture_in_hook(enable: bool) {
|
||||||
|
USE_CAPTURE_HOOK.with(|v| v.set(enable));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_captured_traces_in_hook() -> Vec<TraceInfo> {
|
||||||
|
CAPTURED_TRACES_IN_HOOK.with(|v| v.borrow().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_captured_traces_in_hook() {
|
||||||
|
CAPTURED_TRACES_IN_HOOK.with(|v| v.borrow_mut().clear());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::test_helpers::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scoped_trace_fatal() {
|
||||||
|
enable_capture_in_hook(true);
|
||||||
|
clear_captured_traces_in_hook();
|
||||||
|
|
||||||
|
// Ensure hook is installed
|
||||||
|
crate::internal::test_outcome::TestOutcome::init_current_test_outcome();
|
||||||
|
|
||||||
|
let _ = std::panic::catch_unwind(|| {
|
||||||
|
let _guard = ScopedTraceGuard::new("Second trace".to_string());
|
||||||
|
panic!("Intentional panic");
|
||||||
|
});
|
||||||
|
|
||||||
|
let captured = get_captured_traces_in_hook();
|
||||||
|
assert_eq!(captured.len(), 1);
|
||||||
|
assert_eq!(captured[0].message, "Second trace");
|
||||||
|
|
||||||
|
enable_capture_in_hook(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
third_party/crates/googletest-0.14.3/src/internal/test_data.rs
vendored
Normal file
29
third_party/crates/googletest-0.14.3/src/internal/test_data.rs
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
//! This is for testing only to provide fully-qualified struct paths to macros.
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TestStruct {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub value: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestStruct {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn get_value(&self) -> u32 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GenericTestStruct<T> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub value: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> GenericTestStruct<T> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn get_value<U>(&self, a: U) -> U {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
}
|
||||||
261
third_party/crates/googletest-0.14.3/src/internal/test_filter.rs
vendored
Normal file
261
third_party/crates/googletest-0.14.3/src/internal/test_filter.rs
vendored
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
//! Implements the googletest test filtering protocol.
|
||||||
|
//!
|
||||||
|
//! The Google test filtering protocol consists of the following
|
||||||
|
//! environment variable:
|
||||||
|
//!
|
||||||
|
//! * TESTBRIDGE_TEST_ONLY: string passed from --test_filter
|
||||||
|
//!
|
||||||
|
//! The format of a filter is a ‘,‘-separated list of wildcard
|
||||||
|
//! patterns (called the positive patterns) optionally followed by a
|
||||||
|
//! ‘-’ and another ‘,‘-separated pattern list (called the negative
|
||||||
|
//! patterns). A test matches the filter if and only if it matches any
|
||||||
|
//! of the positive patterns but does not match any of the negative
|
||||||
|
//! patterns. (Note that this is a deliberate devation from GTest
|
||||||
|
//! C++, which uses colons to separate the patterns, as colons will
|
||||||
|
//! unfortunately clash with Rust's "::" namespacing operator.)
|
||||||
|
//!
|
||||||
|
//! As an example: "*mount*-*doom*" will accept any string that contains the
|
||||||
|
//! substring "mount", as long as it also doesn't contain "doom"
|
||||||
|
use crate::internal::glob::{is_glob_pattern, Pattern};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
static TEST_FILTER: OnceLock<Box<dyn TestFilter + Send + Sync>> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn test_should_run(test_name: &str) -> bool {
|
||||||
|
let test_filter = TEST_FILTER.get_or_init(|| {
|
||||||
|
if let Ok(testbridge_test_only) = std::env::var("TESTBRIDGE_TEST_ONLY") {
|
||||||
|
Box::new(get_test_filter(&testbridge_test_only))
|
||||||
|
} else {
|
||||||
|
Box::new(AcceptAll)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test_filter.filter(test_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
trait TestFilter {
|
||||||
|
/// Returns true if the test should run.
|
||||||
|
fn filter(&self, test_name: &str) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AcceptAll;
|
||||||
|
impl TestFilter for AcceptAll {
|
||||||
|
fn filter(&self, _test_name: &str) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Equals(String);
|
||||||
|
impl TestFilter for Equals {
|
||||||
|
fn filter(&self, test_name: &str) -> bool {
|
||||||
|
test_name == self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Matches(Pattern);
|
||||||
|
impl TestFilter for Matches {
|
||||||
|
fn filter(&self, test_name: &str) -> bool {
|
||||||
|
self.0.matches(test_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Collection {
|
||||||
|
// The positive portion:
|
||||||
|
positive_equals: Box<[Equals]>,
|
||||||
|
positive_matches: Box<[Matches]>,
|
||||||
|
|
||||||
|
// The negative portion:
|
||||||
|
negative_equals: Box<[Equals]>,
|
||||||
|
negative_matches: Box<[Matches]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestFilter for Collection {
|
||||||
|
fn filter(&self, test_name: &str) -> bool {
|
||||||
|
(self.positive_equals.iter().any(|f| f.filter(test_name))
|
||||||
|
|| self.positive_matches.iter().any(|f| f.filter(test_name)))
|
||||||
|
&& (!self.negative_equals.iter().any(|f| f.filter(test_name)))
|
||||||
|
&& (!self.negative_matches.iter().any(|f| f.filter(test_name)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_test_filter(testbridge_test_only: &str) -> Collection {
|
||||||
|
let positive_negative: Vec<&str> = testbridge_test_only.splitn(2, '-').collect();
|
||||||
|
|
||||||
|
let (positive_with_globs, positive_literals): (Vec<_>, Vec<_>) = {
|
||||||
|
let positive = positive_negative[0];
|
||||||
|
if positive.is_empty() {
|
||||||
|
// Forces the empty positive filter to accept everything:
|
||||||
|
(vec!["*"], vec![])
|
||||||
|
} else {
|
||||||
|
positive.split(',').partition(|s| is_glob_pattern(s))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (negative_with_globs, negative_literals): (Vec<_>, Vec<_>) = match positive_negative.get(1)
|
||||||
|
{
|
||||||
|
Some(negative) if !negative.is_empty() => {
|
||||||
|
negative.split(',').partition(|s| is_glob_pattern(s))
|
||||||
|
}
|
||||||
|
_ => (vec![], vec![]),
|
||||||
|
};
|
||||||
|
|
||||||
|
Collection {
|
||||||
|
positive_equals: positive_literals.into_iter().map(|s| Equals(s.to_string())).collect(),
|
||||||
|
positive_matches: positive_with_globs
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| Matches(Pattern::new(s.to_string())))
|
||||||
|
.collect(),
|
||||||
|
negative_equals: negative_literals.into_iter().map(|s| Equals(s.to_string())).collect(),
|
||||||
|
negative_matches: negative_with_globs
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| Matches(Pattern::new(s.to_string())))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accept_all_accepts_all() -> Result<()> {
|
||||||
|
let filter = AcceptAll;
|
||||||
|
|
||||||
|
verify_that!(filter.filter(""), is_true())?;
|
||||||
|
verify_that!(filter.filter("abcdefg"), is_true())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_filter_accepts_all() -> Result<()> {
|
||||||
|
let filter = get_test_filter("");
|
||||||
|
|
||||||
|
verify_that!(filter.filter(""), is_true())?;
|
||||||
|
verify_that!(filter.filter("abcdefg"), is_true())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_negation_filter_accepts_all() -> Result<()> {
|
||||||
|
let filter = get_test_filter("-");
|
||||||
|
|
||||||
|
verify_that!(filter.filter(""), is_true())?;
|
||||||
|
verify_that!(filter.filter("abcdefg"), is_true())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_literal_filter() -> Result<()> {
|
||||||
|
let filter = get_test_filter("*magic*");
|
||||||
|
|
||||||
|
verify_that!(filter.filter("this_is_magic"), is_true())?;
|
||||||
|
verify_that!(filter.filter(""), is_false())?;
|
||||||
|
verify_that!(filter.filter("magic"), is_true())?;
|
||||||
|
verify_that!(filter.filter("science"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn star_globs() -> Result<()> {
|
||||||
|
let filter = get_test_filter("a*b");
|
||||||
|
|
||||||
|
verify_that!(filter.filter(""), is_false())?;
|
||||||
|
verify_that!(filter.filter("a"), is_false())?;
|
||||||
|
verify_that!(filter.filter("ab"), is_true())?;
|
||||||
|
verify_that!(filter.filter("a b"), is_true())?;
|
||||||
|
verify_that!(filter.filter("b"), is_false())?;
|
||||||
|
verify_that!(filter.filter("b a"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn question_globs() -> Result<()> {
|
||||||
|
let filter = get_test_filter("a?b");
|
||||||
|
|
||||||
|
verify_that!(filter.filter(""), is_false())?;
|
||||||
|
verify_that!(filter.filter("a"), is_false())?;
|
||||||
|
verify_that!(filter.filter("ab"), is_false())?;
|
||||||
|
verify_that!(filter.filter("aXb"), is_true())?;
|
||||||
|
verify_that!(filter.filter("a b"), is_true())?;
|
||||||
|
verify_that!(filter.filter("b"), is_false())?;
|
||||||
|
verify_that!(filter.filter("b a"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn collection() -> Result<()> {
|
||||||
|
let filter = get_test_filter("a,b");
|
||||||
|
|
||||||
|
verify_that!(filter.filter(""), is_false())?;
|
||||||
|
verify_that!(filter.filter("a"), is_true())?;
|
||||||
|
verify_that!(filter.filter("ab"), is_false())?;
|
||||||
|
verify_that!(filter.filter("a b"), is_false())?;
|
||||||
|
verify_that!(filter.filter("b"), is_true())?;
|
||||||
|
verify_that!(filter.filter("b a"), is_false())?;
|
||||||
|
verify_that!(filter.filter("c"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn collection_with_globs() -> Result<()> {
|
||||||
|
let filter = get_test_filter("*test1*,*test2*");
|
||||||
|
|
||||||
|
verify_that!(filter.filter(""), is_false())?;
|
||||||
|
verify_that!(filter.filter("this is test1"), is_true())?;
|
||||||
|
verify_that!(filter.filter("and test2 is it"), is_true())?;
|
||||||
|
verify_that!(filter.filter("but test3 is not"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn collection_with_globs_negation() -> Result<()> {
|
||||||
|
let filter = get_test_filter("*test*-*testbad");
|
||||||
|
|
||||||
|
verify_that!(filter.filter(""), is_false())?;
|
||||||
|
verify_that!(filter.filter("module"), is_false())?;
|
||||||
|
verify_that!(filter.filter("module::my_test1"), is_true())?;
|
||||||
|
verify_that!(filter.filter("module::my_test2"), is_true())?;
|
||||||
|
verify_that!(filter.filter("module::my_testbad"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mount_doom() -> Result<()> {
|
||||||
|
let filter = get_test_filter("*mount*-*doom*");
|
||||||
|
|
||||||
|
verify_that!(filter.filter(""), is_false())?;
|
||||||
|
verify_that!(filter.filter("mount rushmore"), is_true())?;
|
||||||
|
verify_that!(filter.filter("doom mount"), is_false())?;
|
||||||
|
verify_that!(filter.filter("dismount"), is_true())?;
|
||||||
|
verify_that!(filter.filter("mountains of moria"), is_true())?;
|
||||||
|
verify_that!(filter.filter("frodo and sam went to mount doom"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn collection_with_only_negation() -> Result<()> {
|
||||||
|
let filter = get_test_filter("-testbad1,testbad2");
|
||||||
|
|
||||||
|
verify_that!(filter.filter(""), is_true())?;
|
||||||
|
verify_that!(filter.filter("test"), is_true())?;
|
||||||
|
verify_that!(filter.filter("testbad1"), is_false())?;
|
||||||
|
verify_that!(filter.filter("testbad2"), is_false())?;
|
||||||
|
verify_that!(filter.filter("testbad3"), is_true())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn magic_words_a_and_e() -> Result<()> {
|
||||||
|
let filter = get_test_filter("a*,e*-abracadabra,elbereth,avada kedavra");
|
||||||
|
|
||||||
|
verify_that!(filter.filter("alakazam"), is_true())?;
|
||||||
|
verify_that!(filter.filter("abracadabra"), is_false())?;
|
||||||
|
verify_that!(filter.filter("avada kedavra"), is_false())?;
|
||||||
|
verify_that!(filter.filter("enchantment"), is_true())?;
|
||||||
|
verify_that!(filter.filter("elbereth"), is_false())?;
|
||||||
|
verify_that!(filter.filter("expecto patronum"), is_true())?;
|
||||||
|
verify_that!(filter.filter("fuego"), is_false())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
325
third_party/crates/googletest-0.14.3/src/internal/test_outcome.rs
vendored
Normal file
325
third_party/crates/googletest-0.14.3/src/internal/test_outcome.rs
vendored
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
// Copyright 2022 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::cell::{RefCell, RefMut};
|
||||||
|
use std::fmt::{Debug, Display, Error, Formatter};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use std::thread_local;
|
||||||
|
|
||||||
|
/// The outcome hitherto of running a test.
|
||||||
|
///
|
||||||
|
/// This is kept as a running record as the test progresses. One can access it
|
||||||
|
/// with `TestOutcome::with_current_test_outcome`.
|
||||||
|
///
|
||||||
|
/// **For internal use only. API stablility is not guaranteed!**
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub enum TestOutcome {
|
||||||
|
/// The test ran or is currently running and no assertions have failed.
|
||||||
|
Success,
|
||||||
|
/// The test ran or is currently running and at least one assertion has
|
||||||
|
/// failed.
|
||||||
|
Failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static CURRENT_TEST_OUTCOME: RefCell<Option<TestOutcome>> = const { RefCell::new(None) };
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestOutcome {
|
||||||
|
/// Resets the current test's [`TestOutcome`].
|
||||||
|
///
|
||||||
|
/// This is intended only for use by the attribute macro `#[gtest]`.
|
||||||
|
///
|
||||||
|
/// **For internal use only. API stablility is not guaranteed!**
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn init_current_test_outcome() {
|
||||||
|
static INSTALL_HOOK: OnceLock<()> = OnceLock::new();
|
||||||
|
INSTALL_HOOK.get_or_init(|| {
|
||||||
|
let prev_hook = std::panic::take_hook();
|
||||||
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
|
let traces = crate::internal::scoped_trace::get_scoped_traces();
|
||||||
|
if !traces.is_empty() {
|
||||||
|
#[cfg(test)]
|
||||||
|
{
|
||||||
|
use crate::internal::scoped_trace::test_helpers::*;
|
||||||
|
let use_capture = USE_CAPTURE_HOOK.with(|v| v.get());
|
||||||
|
if use_capture {
|
||||||
|
let _ = CAPTURED_TRACES_IN_HOOK
|
||||||
|
.with(|v| v.try_borrow_mut().map(|mut b| *b = traces.clone()));
|
||||||
|
prev_hook(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("Google Test trace:");
|
||||||
|
for trace in traces.iter().rev() {
|
||||||
|
eprintln!(" {}:{}: {}", trace.file, trace.line, trace.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev_hook(info);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
Self::with_current_test_outcome(|mut current_test_outcome| {
|
||||||
|
*current_test_outcome = Some(TestOutcome::Success);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluates the current test's [`TestOutcome`], producing a suitable
|
||||||
|
/// `Result`.
|
||||||
|
///
|
||||||
|
/// The parameter `result` is the value returned by the test function
|
||||||
|
/// itself. This returns `Result::Err` with a `Display`-formatted string of
|
||||||
|
/// the error if `result` is `Result::Err`.
|
||||||
|
///
|
||||||
|
/// Otherwise, this returns `Result::Err` precisely when a test failure has
|
||||||
|
/// been recorded with
|
||||||
|
/// [`and_log_failure`](crate::GoogleTestSupport::and_log_failure).
|
||||||
|
///
|
||||||
|
/// **For internal use only. API stablility is not guaranteed!**
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn close_current_test_outcome<E: Display>(
|
||||||
|
inner_result: Result<(), E>,
|
||||||
|
) -> Result<(), TestFailure> {
|
||||||
|
TestOutcome::with_current_test_outcome(|mut outcome| {
|
||||||
|
let outer_result = match &*outcome {
|
||||||
|
Some(TestOutcome::Success) => match inner_result {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(_) => Err(TestFailure),
|
||||||
|
},
|
||||||
|
Some(TestOutcome::Failure) => Err(TestFailure),
|
||||||
|
None => {
|
||||||
|
panic!("No test context found. This indicates a bug in GoogleTest.")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(fatal_assertion_failure) = inner_result {
|
||||||
|
println!("{fatal_assertion_failure}");
|
||||||
|
}
|
||||||
|
*outcome = None;
|
||||||
|
outer_result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a `Result` corresponding to the outcome of the currently running
|
||||||
|
/// test.
|
||||||
|
#[track_caller]
|
||||||
|
pub(crate) fn get_current_test_outcome() -> Result<(), TestAssertionFailure> {
|
||||||
|
TestOutcome::with_current_test_outcome(|mut outcome| {
|
||||||
|
let outcome = outcome
|
||||||
|
.as_mut()
|
||||||
|
.expect("No test context found. This indicates a bug in GoogleTest.");
|
||||||
|
match outcome {
|
||||||
|
TestOutcome::Success => Ok(()),
|
||||||
|
TestOutcome::Failure => Err(TestAssertionFailure::create("Test failed".into())),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records that the currently running test has failed.
|
||||||
|
fn fail_current_test() {
|
||||||
|
TestOutcome::with_current_test_outcome(|mut outcome| {
|
||||||
|
let outcome = outcome
|
||||||
|
.as_mut()
|
||||||
|
.expect("No test context found. This indicates a bug in GoogleTest.");
|
||||||
|
*outcome = TestOutcome::Failure;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs `action` with the [`TestOutcome`] for the currently running test.
|
||||||
|
///
|
||||||
|
/// This is primarily intended for use by assertion macros like
|
||||||
|
/// `expect_that!`.
|
||||||
|
fn with_current_test_outcome<T>(action: impl FnOnce(RefMut<Option<TestOutcome>>) -> T) -> T {
|
||||||
|
CURRENT_TEST_OUTCOME.with(|current_test_outcome| action(current_test_outcome.borrow_mut()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that there is a test context present and panic if there is not.
|
||||||
|
pub(crate) fn ensure_test_context_present() {
|
||||||
|
if FAILURE_REPORTER_HOOK.get().is_some() {
|
||||||
|
// Bypassed because an external runner hook is registered.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TestOutcome::with_current_test_outcome(|outcome| {
|
||||||
|
outcome.as_ref().expect(
|
||||||
|
"
|
||||||
|
No test context found.
|
||||||
|
* Did you annotate the test with gtest?
|
||||||
|
* Is the assertion running in the original test thread?
|
||||||
|
",
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A marking struct indicating that a test has failed.
|
||||||
|
///
|
||||||
|
/// This exists to implement the [Error][std::error::Error] trait. It displays
|
||||||
|
/// to a message indicating that the actual test assertion failure messages are
|
||||||
|
/// in the text above.
|
||||||
|
pub struct TestFailure;
|
||||||
|
|
||||||
|
impl std::error::Error for TestFailure {}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for TestFailure {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||||
|
writeln!(f, "See failure output above")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for TestFailure {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||||
|
writeln!(f, "See failure output above")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A report that a single test assertion failed.
|
||||||
|
///
|
||||||
|
/// **For internal use only. API stablility is not guaranteed!**
|
||||||
|
///
|
||||||
|
/// See pub type Result in googletest/src/lib.rs for the public alias.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TestAssertionFailure {
|
||||||
|
/// A human-readable formatted string describing the error.
|
||||||
|
pub description: String,
|
||||||
|
pub custom_message: Option<String>,
|
||||||
|
pub traces: Vec<crate::internal::scoped_trace::TraceInfo>,
|
||||||
|
location: Location,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A code location.
|
||||||
|
///
|
||||||
|
/// `std::panic::Location` does not provide a constructor, hence we cannot
|
||||||
|
/// construct a fake value.
|
||||||
|
///
|
||||||
|
/// **For internal use only. API stablility is not guaranteed!**
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Location {
|
||||||
|
Real(&'static std::panic::Location<'static>),
|
||||||
|
Fake { file: &'static str, line: u32, column: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Location {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Location::Real(l) => write!(f, "{l}"),
|
||||||
|
Location::Fake { file, line, column } => write!(f, "{file}:{line}:{column}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A hook to capture non fatal test failures. This is a global static, and it
|
||||||
|
/// is initialized exactly once.
|
||||||
|
#[doc(hidden)]
|
||||||
|
static FAILURE_REPORTER_HOOK: OnceLock<fn(&TestAssertionFailure)> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Initializes the failure reporter hook.
|
||||||
|
///
|
||||||
|
/// This is a global static, and it is initialized at most once and returns true
|
||||||
|
/// if the initialization was successful.
|
||||||
|
/// If the hook is already set, this will do nothing and return false.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn set_failure_reporter_hook_if_not_set(capture_fn: fn(&TestAssertionFailure)) -> bool {
|
||||||
|
FAILURE_REPORTER_HOOK.set(capture_fn).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestAssertionFailure {
|
||||||
|
/// Creates a new instance with the given `description`.
|
||||||
|
///
|
||||||
|
/// **For internal use only. API stablility is not guaranteed!**
|
||||||
|
#[track_caller]
|
||||||
|
pub fn create(description: String) -> Self {
|
||||||
|
Self {
|
||||||
|
description,
|
||||||
|
custom_message: None,
|
||||||
|
traces: crate::internal::scoped_trace::get_scoped_traces(),
|
||||||
|
location: Location::Real(std::panic::Location::caller()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set `location`` to a fake value.
|
||||||
|
///
|
||||||
|
/// **For internal use only. API stablility is not guaranteed!**
|
||||||
|
pub fn with_fake_location(mut self, file: &'static str, line: u32, column: u32) -> Self {
|
||||||
|
self.location = Location::Fake { file, line, column };
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn log(&self) {
|
||||||
|
println!("{self}");
|
||||||
|
if let Some(capture_fn) = FAILURE_REPORTER_HOOK.get() {
|
||||||
|
capture_fn(self);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TestOutcome::fail_current_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the file name of the location.
|
||||||
|
pub fn file(&self) -> &'static str {
|
||||||
|
match self.location {
|
||||||
|
Location::Real(l) => l.file(),
|
||||||
|
Location::Fake { file, .. } => file,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the line number of the location.
|
||||||
|
pub fn line(&self) -> u32 {
|
||||||
|
match self.location {
|
||||||
|
Location::Real(l) => l.line(),
|
||||||
|
Location::Fake { line, .. } => line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TestAssertionFailure {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||||
|
writeln!(f, "{}", self.description)?;
|
||||||
|
if let Some(custom_message) = &self.custom_message {
|
||||||
|
writeln!(f, "{custom_message}")?;
|
||||||
|
}
|
||||||
|
if !self.traces.is_empty() {
|
||||||
|
writeln!(f, "Google Test trace:")?;
|
||||||
|
for trace in self.traces.iter().rev() {
|
||||||
|
writeln!(f, " {}:{}: {}", trace.file, trace.line, trace.message)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(f, " at {}", self.location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The standard Rust test harness outputs the TestAssertionFailure with the
|
||||||
|
// Debug trait. We want the output to be formatted, so we use a custom Debug
|
||||||
|
// implementation which defers to Display.
|
||||||
|
impl Debug for TestAssertionFailure {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||||
|
Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: std::error::Error> From<T> for TestAssertionFailure {
|
||||||
|
#[track_caller]
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
TestAssertionFailure::create(format!("{value}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "proptest")]
|
||||||
|
impl From<TestAssertionFailure> for proptest::test_runner::TestCaseError {
|
||||||
|
fn from(value: TestAssertionFailure) -> Self {
|
||||||
|
proptest::test_runner::TestCaseError::Fail(format!("{value}").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
107
third_party/crates/googletest-0.14.3/src/internal/test_sharding.rs
vendored
Normal file
107
third_party/crates/googletest-0.14.3/src/internal/test_sharding.rs
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/// This module implements the googletest test sharding protocol. The Google
|
||||||
|
/// sharding protocol consists of the following environment variables:
|
||||||
|
///
|
||||||
|
/// * GTEST_TOTAL_SHARDS: total number of shards.
|
||||||
|
/// * GTEST_SHARD_INDEX: number of this shard
|
||||||
|
/// * GTEST_SHARD_STATUS_FILE: touch this file to indicate support for sharding.
|
||||||
|
///
|
||||||
|
/// See also <https://google.github.io/googletest/advanced.html>
|
||||||
|
use std::cell::OnceCell;
|
||||||
|
use std::env::{var, var_os};
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::fs::{self, File};
|
||||||
|
use std::num::NonZeroU64;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// Environment variable specifying the total number of test shards.
|
||||||
|
const TEST_TOTAL_SHARDS: &[&str] = &["GTEST_TOTAL_SHARDS", "TEST_TOTAL_SHARDS"];
|
||||||
|
|
||||||
|
/// Environment variable specifyign the index of this test shard.
|
||||||
|
const TEST_SHARD_INDEX: &[&str] = &["GTEST_SHARD_INDEX", "TEST_SHARD_INDEX"];
|
||||||
|
|
||||||
|
/// Environment variable specifying the name of the file we create (or cause a
|
||||||
|
/// timestamp change on) to indicate that we support the sharding protocol.
|
||||||
|
const TEST_SHARD_STATUS_FILE: &[&str] = &["GTEST_SHARD_STATUS_FILE", "TEST_SHARD_STATUS_FILE"];
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static SHARDING: OnceCell<Sharding> = const { OnceCell::new() };
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Sharding {
|
||||||
|
this_shard: u64,
|
||||||
|
total_shards: NonZeroU64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Sharding {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { this_shard: 0, total_shards: NonZeroU64::MIN }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_should_run(test_case_hash: u64) -> bool {
|
||||||
|
SHARDING.with(|sharding_cell| {
|
||||||
|
sharding_cell.get_or_init(Sharding::from_environment).test_should_run(test_case_hash)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_var(keys: &[&str]) -> Option<String> {
|
||||||
|
for key in keys {
|
||||||
|
if let Ok(value) = var(OsStr::new(key)) {
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_var_os(keys: &[&str]) -> Option<OsString> {
|
||||||
|
for key in keys {
|
||||||
|
if let Some(value) = var_os(OsStr::new(key)) {
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sharding {
|
||||||
|
fn test_should_run(&self, test_case_hash: u64) -> bool {
|
||||||
|
(test_case_hash % self.total_shards.get()) == self.this_shard
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_environment() -> Sharding {
|
||||||
|
let this_shard: Option<u64> =
|
||||||
|
{ get_var(TEST_SHARD_INDEX).and_then(|value| value.parse().ok()) };
|
||||||
|
let total_shards: Option<NonZeroU64> = {
|
||||||
|
get_var(TEST_TOTAL_SHARDS)
|
||||||
|
.and_then(|value| value.parse().ok())
|
||||||
|
.and_then(NonZeroU64::new)
|
||||||
|
};
|
||||||
|
|
||||||
|
match (this_shard, total_shards) {
|
||||||
|
(Some(this_shard), Some(total_shards)) if this_shard < total_shards.get() => {
|
||||||
|
if let Some(name) = get_var_os(TEST_SHARD_STATUS_FILE) {
|
||||||
|
let pathbuf = PathBuf::from(name);
|
||||||
|
if let Err(e) = create_status_file(&pathbuf) {
|
||||||
|
eprintln!(
|
||||||
|
"failed to create $GTEST_SHARD_STATUS_FILE file {}: {}",
|
||||||
|
pathbuf.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sharding { this_shard, total_shards }
|
||||||
|
}
|
||||||
|
_ => Sharding::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_status_file(path: &Path) -> std::io::Result<()> {
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
File::create(path).map(|_| ())
|
||||||
|
}
|
||||||
381
third_party/crates/googletest-0.14.3/src/lib.rs
vendored
Normal file
381
third_party/crates/googletest-0.14.3/src/lib.rs
vendored
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
// Copyright 2022 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#![doc = include_str!("../crate_docs.md")]
|
||||||
|
|
||||||
|
extern crate googletest_macro;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate quickcheck;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub mod assertions;
|
||||||
|
pub mod description;
|
||||||
|
pub mod fixtures;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod fmt;
|
||||||
|
pub mod internal;
|
||||||
|
pub mod matcher;
|
||||||
|
pub mod matcher_support;
|
||||||
|
pub mod matchers;
|
||||||
|
|
||||||
|
pub use googletest_macro::__abbreviated_stringify;
|
||||||
|
|
||||||
|
/// Re-exports of the symbols in this crate which are most likely to be used.
|
||||||
|
///
|
||||||
|
/// This includes:
|
||||||
|
/// * All assertion macros,
|
||||||
|
/// * Traits and type definitions normally used by tests, and
|
||||||
|
/// * All built-in matchers.
|
||||||
|
///
|
||||||
|
/// Typically, one imports everything in the prelude in one's test module:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// mod tests {
|
||||||
|
/// use googletest::prelude::*;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::fixtures::{ConsumableFixture, Fixture, FixtureOf, StaticFixture};
|
||||||
|
pub use super::gtest;
|
||||||
|
pub use super::matcher::{Matcher, MatcherBase};
|
||||||
|
pub use super::matchers::*;
|
||||||
|
pub use super::scoped_trace;
|
||||||
|
pub use super::verify_current_test_outcome;
|
||||||
|
pub use super::GoogleTestSupport;
|
||||||
|
pub use super::OrFail;
|
||||||
|
pub use super::Result;
|
||||||
|
// Assert macros
|
||||||
|
pub use super::{
|
||||||
|
add_failure, add_failure_at, assert_false, assert_pred, assert_that, assert_true,
|
||||||
|
expect_eq, expect_false, expect_float_eq, expect_ge, expect_gt, expect_le, expect_lt,
|
||||||
|
expect_ne, expect_near, expect_pred, expect_that, expect_true, fail, succeed, verify_eq,
|
||||||
|
verify_false, verify_float_eq, verify_ge, verify_gt, verify_le, verify_lt, verify_ne,
|
||||||
|
verify_near, verify_pred, verify_that, verify_true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Causes a trace to be included in every test failure message generated
|
||||||
|
/// by code in the current scope.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! scoped_trace {
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
#[allow(clippy::shadow_same, clippy::shadow_unrelated)]
|
||||||
|
let _gtest_trace_guard = $crate::internal::scoped_trace::ScopedTraceGuard::new(
|
||||||
|
format!($($arg)*),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use googletest_macro::gtest;
|
||||||
|
pub use googletest_macro::test;
|
||||||
|
|
||||||
|
use internal::test_outcome::{TestAssertionFailure, TestOutcome};
|
||||||
|
|
||||||
|
/// A `Result` whose `Err` variant indicates a test failure.
|
||||||
|
///
|
||||||
|
/// The assertions [`verify_that!`][crate::verify_that],
|
||||||
|
/// [`verify_pred!`][crate::verify_pred], and [`fail!`][crate::fail] evaluate
|
||||||
|
/// to `Result<()>`. A test function may return `Result<()>` in combination with
|
||||||
|
/// those macros to abort immediately on assertion failure.
|
||||||
|
///
|
||||||
|
/// This can be used with subroutines which may cause the test to fatally fail
|
||||||
|
/// and which return some value needed by the caller. For example:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// fn load_file_content_as_string() -> Result<String> {
|
||||||
|
/// let file_stream = load_file().err_to_test_failure()?;
|
||||||
|
/// Ok(file_stream.to_string())
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The `Err` variant contains a [`TestAssertionFailure`] which carries the data
|
||||||
|
/// of the (fatal) assertion failure which generated this result. Non-fatal
|
||||||
|
/// assertion failures, which log the failure and report the test as having
|
||||||
|
/// failed but allow it to continue running, are not encoded in this type.
|
||||||
|
pub type Result<T, E = TestAssertionFailure> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
/// Returns a [`Result`] corresponding to the outcome of the currently running
|
||||||
|
/// test.
|
||||||
|
///
|
||||||
|
/// This returns `Result::Err` precisely if the current test has recorded at
|
||||||
|
/// least one test assertion failure via [`expect_that!`][crate::expect_that],
|
||||||
|
/// [`expect_pred!`][crate::expect_pred], or
|
||||||
|
/// [`GoogleTestSupport::and_log_failure`]. It can be used in concert with the
|
||||||
|
/// `?` operator to continue execution of the test conditionally on there not
|
||||||
|
/// having been any failure yet.
|
||||||
|
///
|
||||||
|
/// This requires the use of the [`#[gtest]`][crate::gtest] attribute macro.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # /* Make sure this also compiles as a doctest.
|
||||||
|
/// #[gtest]
|
||||||
|
/// # */
|
||||||
|
/// # fn foo() -> u32 { 1 }
|
||||||
|
/// # fn bar() -> u32 { 2 }
|
||||||
|
/// fn should_fail_and_not_execute_last_assertion() -> Result<()> {
|
||||||
|
/// # googletest::internal::test_outcome::TestOutcome::init_current_test_outcome();
|
||||||
|
/// expect_that!(foo(), eq(2)); // May fail, but will not abort the test.
|
||||||
|
/// expect_that!(bar(), gt(1)); // May fail, but will not abort the test.
|
||||||
|
/// verify_current_test_outcome()?; // Aborts the test if one of the previous assertions failed.
|
||||||
|
/// verify_that!(foo(), gt(0)) // Does not execute if the line above aborts.
|
||||||
|
/// }
|
||||||
|
/// # verify_that!(should_fail_and_not_execute_last_assertion(), err(displays_as(contains_substring("Test failed")))).unwrap();
|
||||||
|
/// ```
|
||||||
|
#[track_caller]
|
||||||
|
pub fn verify_current_test_outcome() -> Result<()> {
|
||||||
|
TestOutcome::get_current_test_outcome()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds to `Result` support for GoogleTest Rust functionality.
|
||||||
|
pub trait GoogleTestSupport {
|
||||||
|
/// If `self` is a `Result::Err`, writes to `stdout` a failure report
|
||||||
|
/// and marks the test failed. Otherwise, does nothing.
|
||||||
|
///
|
||||||
|
/// This can be used for non-fatal test assertions, for example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # use googletest::internal::test_outcome::TestOutcome;
|
||||||
|
/// # TestOutcome::init_current_test_outcome();
|
||||||
|
/// let actual = 42;
|
||||||
|
/// verify_that!(actual, eq(42)).and_log_failure();
|
||||||
|
/// // Test still passing; nothing happens
|
||||||
|
/// verify_that!(actual, eq(10)).and_log_failure();
|
||||||
|
/// // Test now fails and failure output to stdout
|
||||||
|
/// verify_that!(actual, eq(100)).and_log_failure();
|
||||||
|
/// // Test still fails and new failure also output to stdout
|
||||||
|
/// # TestOutcome::close_current_test_outcome::<&str>(Ok(())).unwrap_err();
|
||||||
|
/// ```
|
||||||
|
fn and_log_failure(self);
|
||||||
|
|
||||||
|
/// If `self` is a `Result::Err`, writes to `stdout` with a failure report
|
||||||
|
/// and the message returned by `provider`.
|
||||||
|
///
|
||||||
|
/// This is equivalent to combining [`GoogleTestSupport::and_log_failure`]
|
||||||
|
/// with a call to [`GoogleTestSupport::with_failure_message`].
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::GoogleTestSupport;
|
||||||
|
/// # use googletest::assertions::verify_eq;
|
||||||
|
/// # use googletest::internal::test_outcome::TestOutcome;
|
||||||
|
/// # TestOutcome::init_current_test_outcome();
|
||||||
|
/// let actual = 0;
|
||||||
|
/// verify_eq!(actual, 42)
|
||||||
|
/// .and_log_failure_with_message(|| format!("Actual {} was wrong!", actual));
|
||||||
|
/// # TestOutcome::close_current_test_outcome::<&str>(Ok(())).unwrap_err();
|
||||||
|
/// ```
|
||||||
|
fn and_log_failure_with_message(self, provider: impl FnOnce() -> String)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.with_failure_message(provider).and_log_failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds `message` to the logged failure message if `self` is a
|
||||||
|
/// `Result::Err`. Otherwise, does nothing.
|
||||||
|
///
|
||||||
|
/// If this method is called more than once, only `message` from the last
|
||||||
|
/// invocation is output.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # fn should_fail() -> Result<()> {
|
||||||
|
/// let actual = 0;
|
||||||
|
/// verify_that!(actual, eq(42)).failure_message("Actual was wrong!")?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual was wrong"))))
|
||||||
|
/// # .unwrap();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// results in the following failure message:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// Expected: actual equal to 42
|
||||||
|
/// but was: 0
|
||||||
|
/// Actual was wrong!
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// One can pass a `String` too:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # fn should_fail() -> Result<()> {
|
||||||
|
/// let actual = 0;
|
||||||
|
/// verify_that!(actual, eq(42))
|
||||||
|
/// .failure_message(format!("Actual {} was wrong!", actual))?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual 0 was wrong"))))
|
||||||
|
/// # .unwrap();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// However, consider using [`GoogleTestSupport::with_failure_message`]
|
||||||
|
/// instead in that case to avoid unnecessary memory allocation when the
|
||||||
|
/// message is not needed.
|
||||||
|
fn failure_message(self, message: impl Into<String>) -> Self;
|
||||||
|
|
||||||
|
/// Adds the output of the closure `provider` to the logged failure message
|
||||||
|
/// if `self` is a `Result::Err`. Otherwise, does nothing.
|
||||||
|
///
|
||||||
|
/// This is analogous to [`GoogleTestSupport::failure_message`] but
|
||||||
|
/// only executes the closure `provider` if it actually produces the
|
||||||
|
/// message, thus saving possible memory allocation.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use googletest::prelude::*;
|
||||||
|
/// # fn should_fail() -> Result<()> {
|
||||||
|
/// let actual = 0;
|
||||||
|
/// verify_that!(actual, eq(42))
|
||||||
|
/// .with_failure_message(|| format!("Actual {} was wrong!", actual))?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// # verify_that!(should_fail(), err(displays_as(contains_substring("Actual 0 was wrong"))))
|
||||||
|
/// # .unwrap();
|
||||||
|
/// ```
|
||||||
|
fn with_failure_message(self, provider: impl FnOnce() -> String) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> GoogleTestSupport for std::result::Result<T, TestAssertionFailure> {
|
||||||
|
fn and_log_failure(self) {
|
||||||
|
TestOutcome::ensure_test_context_present();
|
||||||
|
if let Err(failure) = self {
|
||||||
|
failure.log();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn failure_message(mut self, message: impl Into<String>) -> Self {
|
||||||
|
if let Err(ref mut failure) = self {
|
||||||
|
failure.custom_message = Some(message.into());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_failure_message(mut self, provider: impl FnOnce() -> String) -> Self {
|
||||||
|
if let Err(ref mut failure) = self {
|
||||||
|
failure.custom_message = Some(provider());
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides an extension method for converting an arbitrary type into
|
||||||
|
/// `googletest`'s [`Result`] type.
|
||||||
|
///
|
||||||
|
/// A type can implement this trait to provide an easy way to return immediately
|
||||||
|
/// from a test in conjunction with the `?` operator. This is useful for
|
||||||
|
/// [`Option`] and [`Result`][std::result::Result] types whose `Result::Err`
|
||||||
|
/// variant does not implement [`std::error::Error`].
|
||||||
|
///
|
||||||
|
/// If `Result::Err` implements [`std::error::Error`] you can just use the `?`
|
||||||
|
/// operator directly.
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// #[test]
|
||||||
|
/// fn should_work() -> googletest::Result<()> {
|
||||||
|
/// let value = something_which_can_fail().or_fail()?;
|
||||||
|
/// let value = something_which_can_fail_with_option().or_fail()?;
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn something_which_can_fail() -> std::result::Result<T, String> { ... }
|
||||||
|
/// fn something_which_can_fail_with_option() -> Option<T> { ... }
|
||||||
|
/// ```
|
||||||
|
pub trait OrFail {
|
||||||
|
/// The success type of the test result.
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
/// Converts a value into a [`Result`] containing
|
||||||
|
/// either the [`Self::Output`] type or a [`TestAssertionFailure`].
|
||||||
|
///
|
||||||
|
/// The most frequently used implementations convert
|
||||||
|
/// `Result<T, E>` into `Result<T, TestAssertionFailure>` and
|
||||||
|
/// `Option<T>` into `Result<T, TestAssertionFailure>`.
|
||||||
|
fn or_fail(self) -> Result<Self::Output>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E: std::fmt::Debug> OrFail for std::result::Result<T, E> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn or_fail(self) -> std::result::Result<T, TestAssertionFailure> {
|
||||||
|
match self {
|
||||||
|
Ok(t) => Ok(t),
|
||||||
|
Err(e) => Err(TestAssertionFailure::create(format!("{e:?}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OrFail for Option<T> {
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn or_fail(self) -> std::result::Result<T, TestAssertionFailure> {
|
||||||
|
match self {
|
||||||
|
Some(t) => Ok(t),
|
||||||
|
None => Err(TestAssertionFailure::create(format!(
|
||||||
|
"called `Option::or_fail()` on a `Option::<{}>::None` value",
|
||||||
|
std::any::type_name::<T>()
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub mod __internal_macro_support {
|
||||||
|
use crate::fixtures::Fixture;
|
||||||
|
|
||||||
|
pub struct FixtureTearDownOnDrop<T: Fixture> {
|
||||||
|
fixture: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Fixture> FixtureTearDownOnDrop<T> {
|
||||||
|
pub fn new(fixture: T) -> Self {
|
||||||
|
Self { fixture: Some(fixture) }
|
||||||
|
}
|
||||||
|
pub fn tear_down(mut self) -> crate::Result<()> {
|
||||||
|
if let Some(fixture) = self.fixture.take() {
|
||||||
|
fixture.tear_down()
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Fixture> AsRef<T> for FixtureTearDownOnDrop<T> {
|
||||||
|
fn as_ref(&self) -> &T {
|
||||||
|
self.fixture.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Fixture> AsMut<T> for FixtureTearDownOnDrop<T> {
|
||||||
|
fn as_mut(&mut self) -> &mut T {
|
||||||
|
self.fixture.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Fixture> Drop for FixtureTearDownOnDrop<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(fixture) = self.fixture.take() {
|
||||||
|
fixture.tear_down().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user