diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8c9b3d19..0f3dc598 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -55,11 +55,13 @@ jobs: # This target needs special setup with MinGW. needs-mingw: x86 can-run: true - rustflags: "-C link-arg=-fuse-ld=lld --deny warnings" + # lld on Windows uses extreme amounts of memory for debuginfo=2 + rustflags: "-C link-arg=-fuse-ld=lld -C debuginfo=1 --deny warnings" - os: windows-latest target_triple: i686-pc-windows-msvc can-run: true - rustflags: "-C link-arg=-fuse-ld=lld --deny warnings" + # lld on Windows uses extreme amounts of memory for debuginfo=2 + rustflags: "-C link-arg=-fuse-ld=lld -C debuginfo=1 --deny warnings" - os: ubuntu-latest target_triple: i686-unknown-linux-gnu @@ -74,11 +76,13 @@ jobs: - os: windows-latest target_triple: x86_64-pc-windows-gnu can-run: true - rustflags: "-C link-arg=-fuse-ld=lld --deny warnings" + # lld on Windows uses extreme amounts of memory for debuginfo=2 + rustflags: "-C link-arg=-fuse-ld=lld -C debuginfo=1 --deny warnings" - os: windows-latest target_triple: x86_64-pc-windows-msvc can-run: true - rustflags: "-C link-arg=-fuse-ld=lld --deny warnings" + # lld on Windows uses extreme amounts of memory for debuginfo=2 + rustflags: "-C link-arg=-fuse-ld=lld -C debuginfo=1 --deny warnings" - os: ubuntu-latest target_triple: x86_64-unknown-linux-gnu can-run: true diff --git a/.vscode/settings.json b/.vscode/settings.json index 87a0dab3..871b920e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -65,6 +65,7 @@ "rustdoc", "RUSTFLAGS", "rustfmt", + "Seekable", "SIMD", "smallvec", "snaks", diff --git a/Cargo.lock b/Cargo.lock index a2eb7039..409535d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -949,18 +949,18 @@ checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", @@ -1181,9 +1181,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef75d881185fd2df4a040793927c153d863651108a93c7e17a9e591baa95cc6" +checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" dependencies = [ "serde", "serde_spanned", @@ -1202,9 +1202,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.4" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380f9e8120405471f7c9ad1860a713ef5ece6a670c7eae39225e477340f32fc4" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap", "serde", diff --git a/Cargo.toml b/Cargo.toml index a5f03bbd..a6e8015d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ lto = false # Release should be used for benching, but not actually distributed. [profile.release] lto = "thin" -debug = 2 +debug = 1 # This is the profile used for final binaries distributed via package managers. # It prioritizes performance, and then binary size. We generally don't care about diff --git a/Justfile b/Justfile index b9a0810a..0af23533 100644 --- a/Justfile +++ b/Justfile @@ -105,11 +105,11 @@ test-engine: (gen-tests) # Run the input tests on default features. test-input: - cargo test --test input_implementation -q + cargo test --test input_implementation_tests -q # Run the query tests on default features. test-parser: - cargo test --test query_parser -q + cargo test --test query_parser_tests -q # Run all tests, including real dataset tests, on the feature powerset of the project. test-full: (gen-tests) @@ -241,12 +241,8 @@ commit msg: [private] hook-pre-commit: #!/bin/sh - tmpdiff=$(mktemp -t pre-commit-hook-diff-XXXXXXXX.$$) just assert-benchmarks-committed - git diff --full-index --binary > $tmpdiff - git stash -q --keep-index - (just verify-fmt && just verify-check); \ - git apply --whitespace=nowarn < $tmpdiff}} && git stash drop -q; rm $tmpdiff + (just verify-fmt && just verify-check); [private] @hook-post-checkout: checkout-benchmarks diff --git a/book/src/lib/intro.md b/book/src/lib/intro.md index d4d64663..fae071cf 100644 --- a/book/src/lib/intro.md +++ b/book/src/lib/intro.md @@ -5,7 +5,7 @@ _This part of the book is a work in progress._ ```rust # extern crate rsonpath; use rsonpath::engine::{Compiler, Engine, RsonpathEngine}; -use rsonpath::input::OwnedBytes; +use rsonpath::input::BorrowedBytes; use rsonpath::query::JsonPathQuery; # fn main() -> Result<(), Box> { @@ -29,7 +29,7 @@ let contents = r#" } }"#; -let input = OwnedBytes::new(&contents)?; +let input = BorrowedBytes::new(contents.as_bytes()); let engine = RsonpathEngine::compile_query(&query)?; let count = engine.count(&input)?; diff --git a/crates/rsonpath-benchmarks b/crates/rsonpath-benchmarks index 576bfe10..ec1d4054 160000 --- a/crates/rsonpath-benchmarks +++ b/crates/rsonpath-benchmarks @@ -1 +1 @@ -Subproject commit 576bfe10d45d009d4f1a0f3a94e1b60780451540 +Subproject commit ec1d40542ed6f7e74d550969764f135c5c848d59 diff --git a/crates/rsonpath-lib/proptest-regressions/classification/classifier_correctness_tests.txt b/crates/rsonpath-lib/proptest-regressions/classification/classifier_correctness_tests.txt new file mode 100644 index 00000000..9d777e3e --- /dev/null +++ b/crates/rsonpath-lib/proptest-regressions/classification/classifier_correctness_tests.txt @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 34c43f30d1bf155cf3ee0e13c9776fc11c4dac1f3f19aa602c4c50df5f0e7049 # shrinks to (input, expected) = (",", [Comma(0)]) +cc 24036ddd437694148c11fa1c274956e48ed3792ea084f3923deaa4225d030700 # shrinks to (input, expected) = (",", [Comma(0)]) diff --git a/crates/rsonpath-lib/src/classification/classifier_correctness_tests.rs b/crates/rsonpath-lib/src/classification/classifier_correctness_tests.rs index 6b5d491c..bbe10504 100644 --- a/crates/rsonpath-lib/src/classification/classifier_correctness_tests.rs +++ b/crates/rsonpath-lib/src/classification/classifier_correctness_tests.rs @@ -3,33 +3,39 @@ use crate::{ simd::{self, Simd}, structural::{BracketType, Structural, StructuralIterator}, }, + input::BorrowedBytes, input::Input, - input::OwnedBytes, result::empty::EmptyRecorder, FallibleIterator, }; use super::simd::config_simd; -fn classify_string(json: &str) -> Vec { +fn classify_string(json: &str) -> (Vec, usize) { let simd = simd::configure(); config_simd!(simd => |simd| { let json_string = json.to_owned(); - let bytes = OwnedBytes::try_from(json_string).unwrap(); + let bytes = BorrowedBytes::new(json_string.as_bytes()); let iter = bytes.iter_blocks(&EmptyRecorder); let quotes_classifier = simd.classify_quoted_sequences(iter); let mut structural_classifier = simd.classify_structural_characters(quotes_classifier); structural_classifier.turn_commas_on(0); structural_classifier.turn_colons_on(0); - structural_classifier.collect().unwrap() + (structural_classifier.collect().unwrap(), bytes.leading_padding_len()) }) } +fn apply_offset(vec: &mut [Structural], offset: usize) { + for x in vec { + *x = x.offset(offset); + } +} + #[test] fn empty_string() { - let result = classify_string(""); + let (result, _) = classify_string(""); assert_eq!(Vec::::default(), result); } @@ -37,7 +43,7 @@ fn empty_string() { #[test] fn json() { let json = r#"{"a": [1, 2, 3], "b": "string", "c": {"d": 42, "e": 17}}"#; - let expected: &[Structural] = &[ + let expected: &mut [Structural] = &mut [ Structural::Opening(BracketType::Curly, 0), Structural::Colon(4), Structural::Opening(BracketType::Square, 6), @@ -56,7 +62,8 @@ fn json() { Structural::Closing(BracketType::Curly, 55), ]; - let result = classify_string(json); + let (result, offset) = classify_string(json); + apply_offset(expected, offset); assert_eq!(expected, result); } @@ -64,7 +71,7 @@ fn json() { #[test] fn json_with_escapes() { let json = r#"{"a": "Hello, World!", "b": "\"{Hello, [World]!}\""}"#; - let expected: &[Structural] = &[ + let expected: &mut [Structural] = &mut [ Structural::Opening(BracketType::Curly, 0), Structural::Colon(4), Structural::Comma(21), @@ -72,7 +79,8 @@ fn json_with_escapes() { Structural::Closing(BracketType::Curly, 51), ]; - let result = classify_string(json); + let (result, offset) = classify_string(json); + apply_offset(expected, offset); assert_eq!(expected, result); } @@ -82,7 +90,7 @@ fn reverse_exclamation_point() { let wtf = "¡"; let expected: &[Structural] = &[]; - let result = classify_string(wtf); + let (result, _) = classify_string(wtf); assert_eq!(expected, result); } @@ -92,7 +100,7 @@ fn block_boundary() { use Structural::*; let wtf = r##",,#;0a#0,#a#0#0aa ;a0 0a,"A"#a~A#0a~A##a0|a0#0aaa~ 0#;A|~|"a"A-|;#0 Aa,,"0","A"A0,,,,,,,,,,,,,,,"a",AA;#|#|a;AAA;a A~;aA;A##A#~a ,,,,,,0^A-AA0aa;- ~0,,,#;A;aA#A#0 a-, a;0aaa0|a 0aA -A#a,,,,"\\","##; - let expected: &[Structural] = &[ + let expected: &mut [Structural] = &mut [ Comma(0), Comma(1), Comma(8), @@ -133,13 +141,14 @@ fn block_boundary() { Comma(193), ]; - let result = classify_string(wtf); + let (result, offset) = classify_string(wtf); + apply_offset(expected, offset); assert_eq!(expected, result); } mod prop_test { - use super::{classify_string, BracketType, Structural}; + use super::{apply_offset, classify_string, BracketType, Structural}; use proptest::{self, collection, prelude::*}; use std::fmt::Debug; @@ -275,15 +284,17 @@ mod prop_test { proptest! { #[test] - fn classifies_correctly_ascii((input, expected) in input_string_ascii()) { - let result = classify_string(&input); + fn classifies_correctly_ascii((input, mut expected) in input_string_ascii()) { + let (result, offset) = classify_string(&input); + apply_offset(&mut expected, offset); assert_eq!(expected, result); } #[test] - fn classifies_correctly_all((input, expected) in input_string_all()) { - let result = classify_string(&input); + fn classifies_correctly_all((input, mut expected) in input_string_all()) { + let (result, offset) = classify_string(&input); + apply_offset(&mut expected, offset); assert_eq!(expected, result); } diff --git a/crates/rsonpath-lib/src/classification/memmem.rs b/crates/rsonpath-lib/src/classification/memmem.rs index b8e4ac98..ef5c12cc 100644 --- a/crates/rsonpath-lib/src/classification/memmem.rs +++ b/crates/rsonpath-lib/src/classification/memmem.rs @@ -42,13 +42,13 @@ pub(crate) trait MemmemImpl { type Classifier<'i, 'b, 'r, I, R>: Memmem<'i, 'b, 'r, I, BLOCK_SIZE> where I: Input + 'i, - ::BlockIterator<'i, 'r, BLOCK_SIZE, R>: 'b, + ::BlockIterator<'i, 'r, R, BLOCK_SIZE>: 'b, R: InputRecorder<::Block<'i, BLOCK_SIZE>> + 'r, 'i: 'r; fn memmem<'i, 'b, 'r, I, R>( input: &'i I, - iter: &'b mut ::BlockIterator<'i, 'r, BLOCK_SIZE, R>, + iter: &'b mut ::BlockIterator<'i, 'r, R, BLOCK_SIZE>, ) -> Self::Classifier<'i, 'b, 'r, I, R> where I: Input, diff --git a/crates/rsonpath-lib/src/classification/memmem/avx2_32.rs b/crates/rsonpath-lib/src/classification/memmem/avx2_32.rs index e7e017af..1551caf0 100644 --- a/crates/rsonpath-lib/src/classification/memmem/avx2_32.rs +++ b/crates/rsonpath-lib/src/classification/memmem/avx2_32.rs @@ -1,9 +1,11 @@ use super::{shared::mask_32, shared::vector_256, *}; use crate::{ - input::{error::InputError, Input, InputBlockIterator}, + input::{ + error::{InputError, InputErrorConvertible}, + Input, InputBlockIterator, + }, query::JsonString, result::InputRecorder, - FallibleIterator, }; const SIZE: usize = 32; @@ -14,13 +16,13 @@ impl MemmemImpl for Constructor { type Classifier<'i, 'b, 'r, I, R> = Avx2MemmemClassifier32<'i, 'b, 'r, I, R> where I: Input + 'i, - ::BlockIterator<'i, 'r, BLOCK_SIZE, R>: 'b, + ::BlockIterator<'i, 'r, R, BLOCK_SIZE>: 'b, R: InputRecorder<::Block<'i, BLOCK_SIZE>> + 'r, 'i: 'r; fn memmem<'i, 'b, 'r, I, R>( input: &'i I, - iter: &'b mut ::BlockIterator<'i, 'r, BLOCK_SIZE, R>, + iter: &'b mut ::BlockIterator<'i, 'r, R, BLOCK_SIZE>, ) -> Self::Classifier<'i, 'b, 'r, I, R> where I: Input, @@ -37,7 +39,7 @@ where R: InputRecorder> + 'r, { input: &'i I, - iter: &'b mut I::BlockIterator<'i, 'r, SIZE, R>, + iter: &'b mut I::BlockIterator<'i, 'r, R, SIZE>, } impl<'i, 'b, 'r, I, R> Avx2MemmemClassifier32<'i, 'b, 'r, I, R> @@ -48,7 +50,7 @@ where { #[inline] #[allow(dead_code)] - pub(crate) fn new(input: &'i I, iter: &'b mut I::BlockIterator<'i, 'r, SIZE, R>) -> Self { + pub(crate) fn new(input: &'i I, iter: &'b mut I::BlockIterator<'i, 'r, R, SIZE>) -> Self { Self { input, iter } } @@ -61,13 +63,17 @@ where let classifier = vector_256::BlockClassifier256::new(b'"', b'"'); let mut previous_block: u32 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let classified = classifier.classify_block(&block); let mut result = (previous_block | (classified.first << 1)) & classified.second; while result != 0 { let idx = result.trailing_zeros() as usize; - if self.input.is_member_match(offset + idx - 1, offset + idx, label) { + if self + .input + .is_member_match(offset + idx - 1, offset + idx + 1, label) + .e()? + { return Ok(Some((offset + idx - 1, block))); } result &= !(1 << idx); @@ -92,7 +98,7 @@ where let classifier = vector_256::BlockClassifier256::new(label.bytes()[0], b'"'); let mut previous_block: u32 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let classified = classifier.classify_block(&block); if let Some(res) = mask_32::find_in_mask( @@ -102,7 +108,7 @@ where classified.first, classified.second, offset, - ) { + )? { return Ok(Some((res, block))); } @@ -128,7 +134,7 @@ where let classifier = vector_256::BlockClassifier256::new(label.bytes()[0], label.bytes()[1]); let mut previous_block: u32 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let classified = classifier.classify_block(&block); if let Some(res) = mask_32::find_in_mask( @@ -138,7 +144,7 @@ where classified.first, classified.second, offset, - ) { + )? { return Ok(Some((res, block))); } diff --git a/crates/rsonpath-lib/src/classification/memmem/avx2_64.rs b/crates/rsonpath-lib/src/classification/memmem/avx2_64.rs index 47fc195f..4c94bb84 100644 --- a/crates/rsonpath-lib/src/classification/memmem/avx2_64.rs +++ b/crates/rsonpath-lib/src/classification/memmem/avx2_64.rs @@ -1,10 +1,12 @@ use super::{shared::mask_64, shared::vector_256, *}; use crate::{ classification::mask::m64, - input::{error::InputError, Input, InputBlock, InputBlockIterator}, + input::{ + error::{InputError, InputErrorConvertible}, + Input, InputBlock, InputBlockIterator, + }, query::JsonString, result::InputRecorder, - FallibleIterator, }; const SIZE: usize = 64; @@ -15,13 +17,13 @@ impl MemmemImpl for Constructor { type Classifier<'i, 'b, 'r, I, R> = Avx2MemmemClassifier64<'i, 'b, 'r, I, R> where I: Input + 'i, - ::BlockIterator<'i, 'r, BLOCK_SIZE, R>: 'b, + ::BlockIterator<'i, 'r, R, BLOCK_SIZE>: 'b, R: InputRecorder<::Block<'i, BLOCK_SIZE>> + 'r, 'i: 'r; fn memmem<'i, 'b, 'r, I, R>( input: &'i I, - iter: &'b mut ::BlockIterator<'i, 'r, BLOCK_SIZE, R>, + iter: &'b mut ::BlockIterator<'i, 'r, R, BLOCK_SIZE>, ) -> Self::Classifier<'i, 'b, 'r, I, R> where I: Input, @@ -38,7 +40,7 @@ where R: InputRecorder> + 'r, { input: &'i I, - iter: &'b mut I::BlockIterator<'i, 'r, SIZE, R>, + iter: &'b mut I::BlockIterator<'i, 'r, R, SIZE>, } impl<'i, 'b, 'r, I, R> Avx2MemmemClassifier64<'i, 'b, 'r, I, R> @@ -49,7 +51,7 @@ where { #[inline] #[allow(dead_code)] - pub(crate) fn new(input: &'i I, iter: &'b mut I::BlockIterator<'i, 'r, SIZE, R>) -> Self { + pub(crate) fn new(input: &'i I, iter: &'b mut I::BlockIterator<'i, 'r, R, SIZE>) -> Self { Self { input, iter } } @@ -62,7 +64,7 @@ where let classifier = vector_256::BlockClassifier256::new(b'"', b'"'); let mut previous_block: u64 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let (block1, block2) = block.halves(); let classified1 = classifier.classify_block(block1); let classified2 = classifier.classify_block(block2); @@ -73,7 +75,11 @@ where let mut result = (previous_block | (first_bitmask << 1)) & second_bitmask; while result != 0 { let idx = result.trailing_zeros() as usize; - if self.input.is_member_match(offset + idx - 1, offset + idx, label) { + if self + .input + .is_member_match(offset + idx - 1, offset + idx + 1, label) + .e()? + { return Ok(Some((offset + idx - 1, block))); } result &= !(1 << idx); @@ -98,7 +104,7 @@ where let classifier = vector_256::BlockClassifier256::new(label.bytes()[0], b'"'); let mut previous_block: u64 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let (block1, block2) = block.halves(); let classified1 = classifier.classify_block(block1); let classified2 = classifier.classify_block(block2); @@ -107,7 +113,7 @@ where let second_bitmask = m64::combine_32(classified1.second, classified2.second); if let Some(res) = - mask_64::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset) + mask_64::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset)? { return Ok(Some((res, block))); } @@ -134,7 +140,7 @@ where let classifier = vector_256::BlockClassifier256::new(label.bytes()[0], label.bytes()[1]); let mut previous_block: u64 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let (block1, block2) = block.halves(); let classified1 = classifier.classify_block(block1); let classified2 = classifier.classify_block(block2); @@ -143,7 +149,7 @@ where let second_bitmask = m64::combine_32(classified1.second, classified2.second); if let Some(res) = - mask_64::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset) + mask_64::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset)? { return Ok(Some((res, block))); } diff --git a/crates/rsonpath-lib/src/classification/memmem/nosimd.rs b/crates/rsonpath-lib/src/classification/memmem/nosimd.rs index ca30b602..13f8b81f 100644 --- a/crates/rsonpath-lib/src/classification/memmem/nosimd.rs +++ b/crates/rsonpath-lib/src/classification/memmem/nosimd.rs @@ -1,9 +1,12 @@ use super::*; -use crate::input::error::InputError; -use crate::input::{Input, InputBlockIterator}; -use crate::query::JsonString; -use crate::result::InputRecorder; -use crate::FallibleIterator; +use crate::{ + input::{ + error::{InputError, InputErrorConvertible}, + Input, InputBlockIterator, + }, + query::JsonString, + result::InputRecorder, +}; pub(crate) struct Constructor; @@ -11,13 +14,13 @@ impl MemmemImpl for Constructor { type Classifier<'i, 'b, 'r, I, R> = SequentialMemmemClassifier<'i, 'b, 'r, I, R, BLOCK_SIZE> where I: Input + 'i, - ::BlockIterator<'i, 'r, BLOCK_SIZE, R>: 'b, + ::BlockIterator<'i, 'r, R, BLOCK_SIZE>: 'b, R: InputRecorder<::Block<'i, BLOCK_SIZE>> + 'r, 'i: 'r; fn memmem<'i, 'b, 'r, I, R>( input: &'i I, - iter: &'b mut ::BlockIterator<'i, 'r, BLOCK_SIZE, R>, + iter: &'b mut ::BlockIterator<'i, 'r, R, BLOCK_SIZE>, ) -> Self::Classifier<'i, 'b, 'r, I, R> where I: Input, @@ -34,7 +37,7 @@ where R: InputRecorder> + 'r, { input: &'i I, - iter: &'b mut I::BlockIterator<'i, 'r, N, R>, + iter: &'b mut I::BlockIterator<'i, 'r, R, N>, } impl<'i, 'b, 'r, I, R, const N: usize> SequentialMemmemClassifier<'i, 'b, 'r, I, R, N> @@ -55,14 +58,13 @@ where label.bytes()[0] }; - while let Some(block) = self.iter.next()? { - let res = block.iter().copied().enumerate().find(|&(i, c)| { + while let Some(block) = self.iter.next().e()? { + for (i, c) in block.iter().copied().enumerate() { let j = offset + i; - c == first_c && j > 0 && self.input.is_member_match(j - 1, j + label_size - 2, label) - }); - if let Some((res, _)) = res { - return Ok(Some((res + offset - 1, block))); + if c == first_c && j > 0 && self.input.is_member_match(j - 1, j + label_size - 1, label).e()? { + return Ok(Some((j - 1, block))); + } } offset += block.len(); diff --git a/crates/rsonpath-lib/src/classification/memmem/shared.rs b/crates/rsonpath-lib/src/classification/memmem/shared.rs index 9bc9ce46..29c0801d 100644 --- a/crates/rsonpath-lib/src/classification/memmem/shared.rs +++ b/crates/rsonpath-lib/src/classification/memmem/shared.rs @@ -1,6 +1,10 @@ -use crate::input::error::InputError; -use crate::input::Input; -use crate::query::JsonString; +use crate::{ + input::{ + error::{InputError, InputErrorConvertible}, + Input, + }, + query::JsonString, +}; #[cfg(target_arch = "x86")] pub(super) mod mask_32; @@ -24,12 +28,12 @@ where let block_idx = start_idx % N; let label_size = label.bytes_with_quotes().len(); - let res = first_block[block_idx..].iter().copied().enumerate().find(|&(i, c)| { + for (i, c) in first_block[block_idx..].iter().copied().enumerate() { let j = start_idx + i; - c == b'"' && input.is_member_match(j, j + label_size - 1, label) - }); - if let Some((res, _)) = res { - return Ok(Some((res + start_idx, first_block))); + + if c == b'"' && input.is_member_match(j, j + label_size, label).e()? { + return Ok(Some((j, first_block))); + } } Ok(None) diff --git a/crates/rsonpath-lib/src/classification/memmem/shared/mask_32.rs b/crates/rsonpath-lib/src/classification/memmem/shared/mask_32.rs index a983c040..44630088 100644 --- a/crates/rsonpath-lib/src/classification/memmem/shared/mask_32.rs +++ b/crates/rsonpath-lib/src/classification/memmem/shared/mask_32.rs @@ -1,4 +1,10 @@ -use crate::{input::Input, query::JsonString}; +use crate::{ + input::{ + error::{InputError, InputErrorConvertible}, + Input, + }, + query::JsonString, +}; #[inline(always)] pub(crate) fn find_in_mask( @@ -8,15 +14,19 @@ pub(crate) fn find_in_mask( first: u32, second: u32, offset: usize, -) -> Option { +) -> Result, InputError> { let label_size = label.bytes_with_quotes().len(); let mut result = (previous_block | (first << 1)) & second; while result != 0 { let idx = result.trailing_zeros() as usize; - if offset + idx > 1 && input.is_member_match(offset + idx - 2, offset + idx + label_size - 3, label) { - return Some(offset + idx - 2); + if offset + idx > 1 + && input + .is_member_match(offset + idx - 2, offset + idx + label_size - 2, label) + .e()? + { + return Ok(Some(offset + idx - 2)); } result &= !(1 << idx); } - None + Ok(None) } diff --git a/crates/rsonpath-lib/src/classification/memmem/shared/mask_64.rs b/crates/rsonpath-lib/src/classification/memmem/shared/mask_64.rs index 47e1a5aa..78ca3520 100644 --- a/crates/rsonpath-lib/src/classification/memmem/shared/mask_64.rs +++ b/crates/rsonpath-lib/src/classification/memmem/shared/mask_64.rs @@ -1,4 +1,11 @@ -use crate::{debug, input::Input, query::JsonString}; +use crate::{ + debug, + input::{ + error::{InputError, InputErrorConvertible}, + Input, + }, + query::JsonString, +}; #[inline(always)] pub(crate) fn find_in_mask( @@ -8,16 +15,20 @@ pub(crate) fn find_in_mask( first: u64, second: u64, offset: usize, -) -> Option { +) -> Result, InputError> { let label_size = label.bytes_with_quotes().len(); let mut result = (previous_block | (first << 1)) & second; while result != 0 { let idx = result.trailing_zeros() as usize; debug!("{offset} + {idx} - 2 to {offset} + {idx} + {label_size} - 3"); - if offset + idx > 1 && input.is_member_match(offset + idx - 2, offset + idx + label_size - 3, label) { - return Some(offset + idx - 2); + if offset + idx > 1 + && input + .is_member_match(offset + idx - 2, offset + idx + label_size - 2, label) + .e()? + { + return Ok(Some(offset + idx - 2)); } result &= !(1 << idx); } - None + Ok(None) } diff --git a/crates/rsonpath-lib/src/classification/memmem/sse2_32.rs b/crates/rsonpath-lib/src/classification/memmem/sse2_32.rs index 2a8d374e..88cba04e 100644 --- a/crates/rsonpath-lib/src/classification/memmem/sse2_32.rs +++ b/crates/rsonpath-lib/src/classification/memmem/sse2_32.rs @@ -1,10 +1,12 @@ use super::{shared::mask_32, shared::vector_128, *}; use crate::{ classification::mask::m32, - input::{error::InputError, Input, InputBlock, InputBlockIterator}, + input::{ + error::{InputError, InputErrorConvertible}, + Input, InputBlock, InputBlockIterator, + }, query::JsonString, result::InputRecorder, - FallibleIterator, }; const SIZE: usize = 32; @@ -15,13 +17,13 @@ impl MemmemImpl for Constructor { type Classifier<'i, 'b, 'r, I, R> = Sse2MemmemClassifier32<'i, 'b, 'r, I, R> where I: Input + 'i, - ::BlockIterator<'i, 'r, BLOCK_SIZE, R>: 'b, + ::BlockIterator<'i, 'r, R, BLOCK_SIZE>: 'b, R: InputRecorder<::Block<'i, BLOCK_SIZE>> + 'r, 'i: 'r; fn memmem<'i, 'b, 'r, I, R>( input: &'i I, - iter: &'b mut ::BlockIterator<'i, 'r, BLOCK_SIZE, R>, + iter: &'b mut ::BlockIterator<'i, 'r, R, BLOCK_SIZE>, ) -> Self::Classifier<'i, 'b, 'r, I, R> where I: Input, @@ -38,7 +40,7 @@ where R: InputRecorder> + 'r, { input: &'i I, - iter: &'b mut I::BlockIterator<'i, 'r, SIZE, R>, + iter: &'b mut I::BlockIterator<'i, 'r, R, SIZE>, } impl<'i, 'b, 'r, I, R> Sse2MemmemClassifier32<'i, 'b, 'r, I, R> @@ -49,7 +51,7 @@ where { #[inline] #[allow(dead_code)] - pub(crate) fn new(input: &'i I, iter: &'b mut I::BlockIterator<'i, 'r, SIZE, R>) -> Self { + pub(crate) fn new(input: &'i I, iter: &'b mut I::BlockIterator<'i, 'r, R, SIZE>) -> Self { Self { input, iter } } @@ -62,7 +64,7 @@ where let classifier = vector_128::BlockClassifier128::new(b'"', b'"'); let mut previous_block: u32 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let (block1, block2) = block.halves(); let classified1 = classifier.classify_block(block1); let classified2 = classifier.classify_block(block2); @@ -73,7 +75,11 @@ where let mut result = (previous_block | (first_bitmask << 1)) & second_bitmask; while result != 0 { let idx = result.trailing_zeros() as usize; - if self.input.is_member_match(offset + idx - 1, offset + idx, label) { + if self + .input + .is_member_match(offset + idx - 1, offset + idx + 1, label) + .e()? + { return Ok(Some((offset + idx - 1, block))); } result &= !(1 << idx); @@ -98,7 +104,7 @@ where let classifier = vector_128::BlockClassifier128::new(label.bytes()[0], b'"'); let mut previous_block: u32 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let (block1, block2) = block.halves(); let classified1 = classifier.classify_block(block1); let classified2 = classifier.classify_block(block2); @@ -107,7 +113,7 @@ where let second_bitmask = m32::combine_16(classified1.second, classified2.second); if let Some(res) = - mask_32::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset) + mask_32::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset)? { return Ok(Some((res, block))); } @@ -134,7 +140,7 @@ where let classifier = vector_128::BlockClassifier128::new(label.bytes()[0], label.bytes()[1]); let mut previous_block: u32 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let (block1, block2) = block.halves(); let classified1 = classifier.classify_block(block1); let classified2 = classifier.classify_block(block2); @@ -143,7 +149,7 @@ where let second_bitmask = m32::combine_16(classified1.second, classified2.second); if let Some(res) = - mask_32::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset) + mask_32::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset)? { return Ok(Some((res, block))); } diff --git a/crates/rsonpath-lib/src/classification/memmem/sse2_64.rs b/crates/rsonpath-lib/src/classification/memmem/sse2_64.rs index abaece3c..4cbe9bac 100644 --- a/crates/rsonpath-lib/src/classification/memmem/sse2_64.rs +++ b/crates/rsonpath-lib/src/classification/memmem/sse2_64.rs @@ -1,10 +1,12 @@ use super::{shared::mask_64, shared::vector_128, *}; use crate::{ classification::mask::m64, - input::{error::InputError, Input, InputBlock, InputBlockIterator}, + input::{ + error::{InputError, InputErrorConvertible}, + Input, InputBlock, InputBlockIterator, + }, query::JsonString, result::InputRecorder, - FallibleIterator, }; const SIZE: usize = 64; @@ -15,13 +17,13 @@ impl MemmemImpl for Constructor { type Classifier<'i, 'b, 'r, I, R> = Sse2MemmemClassifier64<'i, 'b, 'r, I, R> where I: Input + 'i, - ::BlockIterator<'i, 'r, BLOCK_SIZE, R>: 'b, + ::BlockIterator<'i, 'r, R, BLOCK_SIZE>: 'b, R: InputRecorder<::Block<'i, BLOCK_SIZE>> + 'r, 'i: 'r; fn memmem<'i, 'b, 'r, I, R>( input: &'i I, - iter: &'b mut ::BlockIterator<'i, 'r, BLOCK_SIZE, R>, + iter: &'b mut ::BlockIterator<'i, 'r, R, BLOCK_SIZE>, ) -> Self::Classifier<'i, 'b, 'r, I, R> where I: Input, @@ -38,7 +40,7 @@ where R: InputRecorder> + 'r, { input: &'i I, - iter: &'b mut I::BlockIterator<'i, 'r, SIZE, R>, + iter: &'b mut I::BlockIterator<'i, 'r, R, SIZE>, } impl<'i, 'b, 'r, I, R> Sse2MemmemClassifier64<'i, 'b, 'r, I, R> @@ -49,7 +51,7 @@ where { #[inline] #[allow(dead_code)] - pub(crate) fn new(input: &'i I, iter: &'b mut I::BlockIterator<'i, 'r, SIZE, R>) -> Self { + pub(crate) fn new(input: &'i I, iter: &'b mut I::BlockIterator<'i, 'r, R, SIZE>) -> Self { Self { input, iter } } @@ -62,7 +64,7 @@ where let classifier = vector_128::BlockClassifier128::new(b'"', b'"'); let mut previous_block: u64 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let (block1, block2, block3, block4) = block.quarters(); let classified1 = classifier.classify_block(block1); let classified2 = classifier.classify_block(block2); @@ -85,7 +87,11 @@ where let mut result = (previous_block | (first_bitmask << 1)) & second_bitmask; while result != 0 { let idx = result.trailing_zeros() as usize; - if self.input.is_member_match(offset + idx - 1, offset + idx, label) { + if self + .input + .is_member_match(offset + idx - 1, offset + idx + 1, label) + .e()? + { return Ok(Some((offset + idx - 1, block))); } result &= !(1 << idx); @@ -110,7 +116,7 @@ where let classifier = vector_128::BlockClassifier128::new(label.bytes()[0], b'"'); let mut previous_block: u64 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let (block1, block2, block3, block4) = block.quarters(); let classified1 = classifier.classify_block(block1); let classified2 = classifier.classify_block(block2); @@ -131,7 +137,7 @@ where ); if let Some(res) = - mask_64::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset) + mask_64::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset)? { return Ok(Some((res, block))); } @@ -158,7 +164,7 @@ where let classifier = vector_128::BlockClassifier128::new(label.bytes()[0], label.bytes()[1]); let mut previous_block: u64 = 0; - while let Some(block) = self.iter.next()? { + while let Some(block) = self.iter.next().e()? { let (block1, block2, block3, block4) = block.quarters(); let classified1 = classifier.classify_block(block1); let classified2 = classifier.classify_block(block2); @@ -179,7 +185,7 @@ where ); if let Some(res) = - mask_64::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset) + mask_64::find_in_mask(self.input, label, previous_block, first_bitmask, second_bitmask, offset)? { return Ok(Some((res, block))); } diff --git a/crates/rsonpath-lib/src/classification/quotes/avx2_32.rs b/crates/rsonpath-lib/src/classification/quotes/avx2_32.rs index 101ffa69..436ecb27 100644 --- a/crates/rsonpath-lib/src/classification/quotes/avx2_32.rs +++ b/crates/rsonpath-lib/src/classification/quotes/avx2_32.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{ block, debug, - input::{InputBlock, InputBlockIterator}, + input::{error::InputErrorConvertible, InputBlock, InputBlockIterator}, FallibleIterator, }; use std::marker::PhantomData; diff --git a/crates/rsonpath-lib/src/classification/quotes/avx2_64.rs b/crates/rsonpath-lib/src/classification/quotes/avx2_64.rs index 257d44ba..6634672c 100644 --- a/crates/rsonpath-lib/src/classification/quotes/avx2_64.rs +++ b/crates/rsonpath-lib/src/classification/quotes/avx2_64.rs @@ -6,7 +6,7 @@ use crate::{ block, classification::mask::m64, debug, - input::{InputBlock, InputBlockIterator}, + input::{error::InputErrorConvertible, InputBlock, InputBlockIterator}, FallibleIterator, }; use std::marker::PhantomData; diff --git a/crates/rsonpath-lib/src/classification/quotes/nosimd.rs b/crates/rsonpath-lib/src/classification/quotes/nosimd.rs index f62f8d7c..923f3f74 100644 --- a/crates/rsonpath-lib/src/classification/quotes/nosimd.rs +++ b/crates/rsonpath-lib/src/classification/quotes/nosimd.rs @@ -1,5 +1,9 @@ use super::*; -use crate::{debug, input::InputBlockIterator, FallibleIterator, MaskType}; +use crate::{ + debug, + input::{error::InputErrorConvertible, InputBlockIterator}, + FallibleIterator, MaskType, +}; use std::marker::PhantomData; pub(crate) struct Constructor; @@ -98,7 +102,7 @@ where #[inline(always)] fn next(&mut self) -> Result, InputError> { - match self.iter.next()? { + match self.iter.next().e()? { Some(block) => Ok(Some(self.classify_block(block))), None => Ok(None), } @@ -127,7 +131,7 @@ where debug!("Offsetting by {count}"); for _ in 0..count - 1 { - self.iter.next()?; + self.iter.next().e()?; } self.next() diff --git a/crates/rsonpath-lib/src/classification/quotes/shared.rs b/crates/rsonpath-lib/src/classification/quotes/shared.rs index 18b8b348..2f6d15bb 100644 --- a/crates/rsonpath-lib/src/classification/quotes/shared.rs +++ b/crates/rsonpath-lib/src/classification/quotes/shared.rs @@ -77,7 +77,7 @@ macro_rules! quote_classifier { #[inline(always)] fn next(&mut self) -> Result, Self::Error> { - match self.iter.next()? { + match self.iter.next().e()? { Some(block) => { // SAFETY: target_feature invariant let mask = unsafe { self.classifier.classify(&block) }; @@ -107,7 +107,7 @@ macro_rules! quote_classifier { debug!("Offsetting by {count}"); for _ in 0..count - 1 { - self.iter.next()?; + self.iter.next().e()?; } self.next() diff --git a/crates/rsonpath-lib/src/classification/quotes/sse2_32.rs b/crates/rsonpath-lib/src/classification/quotes/sse2_32.rs index acf83e60..e4e4b466 100644 --- a/crates/rsonpath-lib/src/classification/quotes/sse2_32.rs +++ b/crates/rsonpath-lib/src/classification/quotes/sse2_32.rs @@ -6,7 +6,7 @@ use crate::{ block, classification::mask::m32, debug, - input::{InputBlock, InputBlockIterator}, + input::{error::InputErrorConvertible, InputBlock, InputBlockIterator}, FallibleIterator, }; use std::marker::PhantomData; diff --git a/crates/rsonpath-lib/src/classification/quotes/sse2_64.rs b/crates/rsonpath-lib/src/classification/quotes/sse2_64.rs index a36bde58..eff0ade7 100644 --- a/crates/rsonpath-lib/src/classification/quotes/sse2_64.rs +++ b/crates/rsonpath-lib/src/classification/quotes/sse2_64.rs @@ -6,7 +6,7 @@ use crate::{ block, classification::mask::m64, debug, - input::{InputBlock, InputBlockIterator}, + input::{error::InputErrorConvertible, InputBlock, InputBlockIterator}, FallibleIterator, }; use std::marker::PhantomData; diff --git a/crates/rsonpath-lib/src/classification/simd.rs b/crates/rsonpath-lib/src/classification/simd.rs index 75ba22af..02c8c478 100644 --- a/crates/rsonpath-lib/src/classification/simd.rs +++ b/crates/rsonpath-lib/src/classification/simd.rs @@ -238,7 +238,7 @@ pub(crate) trait Simd: Copy { type MemmemClassifier<'i, 'b, 'r, I, R>: Memmem<'i, 'b, 'r, I, BLOCK_SIZE> where I: Input + 'i, - ::BlockIterator<'i, 'r, BLOCK_SIZE, R>: 'b, + ::BlockIterator<'i, 'r, R, BLOCK_SIZE>: 'b, R: InputRecorder<::Block<'i, BLOCK_SIZE>> + 'r, 'i: 'r; @@ -319,7 +319,7 @@ pub(crate) trait Simd: Copy { fn memmem<'i, 'b, 'r, I, R>( self, input: &'i I, - iter: &'b mut ::BlockIterator<'i, 'r, BLOCK_SIZE, R>, + iter: &'b mut ::BlockIterator<'i, 'r, R, BLOCK_SIZE>, ) -> Self::MemmemClassifier<'i, 'b, 'r, I, R> where I: Input, @@ -367,7 +367,7 @@ where type MemmemClassifier<'i, 'b, 'r, I, R> = M::Classifier<'i, 'b, 'r, I, R> where I: Input + 'i, - ::BlockIterator<'i, 'r, BLOCK_SIZE, R>: 'b, + ::BlockIterator<'i, 'r, R, BLOCK_SIZE>: 'b, R: InputRecorder<::Block<'i, BLOCK_SIZE>> + 'r, 'i: 'r; @@ -453,7 +453,7 @@ where fn memmem<'i, 'b, 'r, I, R>( self, input: &'i I, - iter: &'b mut ::BlockIterator<'i, 'r, BLOCK_SIZE, R>, + iter: &'b mut ::BlockIterator<'i, 'r, R, BLOCK_SIZE>, ) -> Self::MemmemClassifier<'i, 'b, 'r, I, R> where I: Input, @@ -944,18 +944,20 @@ cfg_if! { else { macro_rules! config_simd { ($conf:expr => |$simd:ident| $b:block) => { - let conf = $conf; - assert_eq!(conf.highest_simd(), $crate::classification::simd::SimdTag::Nosimd); - assert!(!conf.fast_quotes()); - assert!(!conf.fast_popcnt()); - let $simd = $crate::classification::simd::ResolvedSimd::< - $crate::classification::quotes::nosimd::Constructor, - $crate::classification::structural::nosimd::Constructor, - $crate::classification::depth::nosimd::Constructor, - $crate::classification::memmem::nosimd::Constructor, - {$crate::classification::simd::NOSIMD}, - >::new(); - $b + { + let conf = $conf; + assert_eq!(conf.highest_simd(), $crate::classification::simd::SimdTag::Nosimd); + assert!(!conf.fast_quotes()); + assert!(!conf.fast_popcnt()); + let $simd = $crate::classification::simd::ResolvedSimd::< + $crate::classification::quotes::nosimd::Constructor, + $crate::classification::structural::nosimd::Constructor, + $crate::classification::depth::nosimd::Constructor, + $crate::classification::memmem::nosimd::Constructor, + {$crate::classification::simd::NOSIMD}, + >::new(); + $b + } }; } } diff --git a/crates/rsonpath-lib/src/classification/structural.rs b/crates/rsonpath-lib/src/classification/structural.rs index 46e2a6ce..804fe1ce 100644 --- a/crates/rsonpath-lib/src/classification/structural.rs +++ b/crates/rsonpath-lib/src/classification/structural.rs @@ -206,7 +206,7 @@ mod tests { use super::*; use crate::{ classification::simd::{self, config_simd, Simd}, - input::{Input, OwnedBytes}, + input::{BorrowedBytes, Input}, result::empty::EmptyRecorder, }; @@ -219,21 +219,22 @@ mod tests { config_simd!(simd => |simd| { let json = r#"{"a": [42, 36, { "b": { "c": 1, "d": 2 } }]}"#; let json_string = json.to_owned(); - let input = OwnedBytes::new(&json_string).unwrap(); + let input = BorrowedBytes::new(json_string.as_bytes()); let iter = input.iter_blocks(&EmptyRecorder); let quotes = simd.classify_quoted_sequences(iter); + let offset = input.leading_padding_len(); let mut classifier = simd.classify_structural_characters(quotes); - assert_eq!(Some(Opening(Curly, 0)), classifier.next().unwrap()); - assert_eq!(Some(Opening(Square, 6)), classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, offset)), classifier.next().unwrap()); + assert_eq!(Some(Opening(Square, 6 + offset)), classifier.next().unwrap()); let resume_state = classifier.stop(); let mut resumed_classifier = simd.resume_structural_classification(resume_state); - assert_eq!(Some(Opening(Curly, 15)), resumed_classifier.next().unwrap()); - assert_eq!(Some(Opening(Curly, 22)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, 15 + offset)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, 22 + offset)), resumed_classifier.next().unwrap()); }); } @@ -246,25 +247,26 @@ mod tests { config_simd!(simd => |simd| { let json = r#"{"a": [42, 36, { "b": { "c": 1, "d": 2 } }]}"#; let json_string = json.to_owned(); - let input = OwnedBytes::new(&json_string).unwrap(); + let input = BorrowedBytes::new(json_string.as_bytes()); let iter = input.iter_blocks(&EmptyRecorder); let quotes = simd.classify_quoted_sequences(iter); + let offset = input.leading_padding_len(); let mut classifier = simd.classify_structural_characters(quotes); classifier.turn_commas_on(0); - assert_eq!(Some(Opening(Curly, 0)), classifier.next().unwrap()); - assert_eq!(Some(Opening(Square, 6)), classifier.next().unwrap()); - assert_eq!(Some(Comma(9)), classifier.next().unwrap()); - assert_eq!(Some(Comma(13)), classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, offset)), classifier.next().unwrap()); + assert_eq!(Some(Opening(Square, 6 + offset)), classifier.next().unwrap()); + assert_eq!(Some(Comma(9 + offset)), classifier.next().unwrap()); + assert_eq!(Some(Comma(13 + offset)), classifier.next().unwrap()); let resume_state = classifier.stop(); let mut resumed_classifier = simd.resume_structural_classification(resume_state); - assert_eq!(Some(Opening(Curly, 15)), resumed_classifier.next().unwrap()); - assert_eq!(Some(Opening(Curly, 22)), resumed_classifier.next().unwrap()); - assert_eq!(Some(Comma(30)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, 15 + offset)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, 22 + offset)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Comma(30 + offset)), resumed_classifier.next().unwrap()); }); } @@ -277,25 +279,26 @@ mod tests { config_simd!(simd => |simd| { let json = r#"{"a": [42, 36, { "b": { "c": 1, "d": 2 } }]}"#; let json_string = json.to_owned(); - let input = OwnedBytes::new(&json_string).unwrap(); + let input = BorrowedBytes::new(json_string.as_bytes()); let iter = input.iter_blocks(&EmptyRecorder); let quotes = simd.classify_quoted_sequences(iter); + let offset = input.leading_padding_len(); let mut classifier = simd.classify_structural_characters(quotes); classifier.turn_colons_on(0); - assert_eq!(Some(Opening(Curly, 0)), classifier.next().unwrap()); - assert_eq!(Some(Colon(4)), classifier.next().unwrap()); - assert_eq!(Some(Opening(Square, 6)), classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, offset)), classifier.next().unwrap()); + assert_eq!(Some(Colon(4 + offset)), classifier.next().unwrap()); + assert_eq!(Some(Opening(Square, 6 + offset)), classifier.next().unwrap()); let resume_state = classifier.stop(); let mut resumed_classifier = simd.resume_structural_classification(resume_state); - assert_eq!(Some(Opening(Curly, 15)), resumed_classifier.next().unwrap()); - assert_eq!(Some(Colon(20)), resumed_classifier.next().unwrap()); - assert_eq!(Some(Opening(Curly, 22)), resumed_classifier.next().unwrap()); - assert_eq!(Some(Colon(27)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, 15 + offset)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Colon(20 + offset)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, 22 + offset)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Colon(27 + offset)), resumed_classifier.next().unwrap()); }); } @@ -308,29 +311,56 @@ mod tests { config_simd!(simd => |simd| { let json = r#"{"a": [42, 36, { "b": { "c": 1, "d": 2 } }]}"#; let json_string = json.to_owned(); - let input = OwnedBytes::new(&json_string).unwrap(); + let input = BorrowedBytes::new(json_string.as_bytes()); let iter = input.iter_blocks(&EmptyRecorder); let quotes = simd.classify_quoted_sequences(iter); + let offset = input.leading_padding_len(); let mut classifier = simd.classify_structural_characters(quotes); classifier.turn_commas_on(0); classifier.turn_colons_on(0); - assert_eq!(Some(Opening(Curly, 0)), classifier.next().unwrap()); - assert_eq!(Some(Colon(4)), classifier.next().unwrap()); - assert_eq!(Some(Opening(Square, 6)), classifier.next().unwrap()); - assert_eq!(Some(Comma(9)), classifier.next().unwrap()); - assert_eq!(Some(Comma(13)), classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, offset)), classifier.next().unwrap()); + assert_eq!(Some(Colon(4 + offset)), classifier.next().unwrap()); + assert_eq!(Some(Opening(Square, 6 + offset)), classifier.next().unwrap()); + assert_eq!(Some(Comma(9 + offset)), classifier.next().unwrap()); + assert_eq!(Some(Comma(13 + offset)), classifier.next().unwrap()); let resume_state = classifier.stop(); let mut resumed_classifier = simd.resume_structural_classification(resume_state); - assert_eq!(Some(Opening(Curly, 15)), resumed_classifier.next().unwrap()); - assert_eq!(Some(Colon(20)), resumed_classifier.next().unwrap()); - assert_eq!(Some(Opening(Curly, 22)), resumed_classifier.next().unwrap()); - assert_eq!(Some(Colon(27)), resumed_classifier.next().unwrap()); - assert_eq!(Some(Comma(30)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, 15 + offset)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Colon(20 + offset)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Opening(Curly, 22 + offset)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Colon(27 + offset)), resumed_classifier.next().unwrap()); + assert_eq!(Some(Comma(30 + offset)), resumed_classifier.next().unwrap()); + }); + } + + #[test] + fn resumption_at_block_boundary() { + use BracketType::*; + use Structural::*; + + let simd = simd::configure(); + config_simd!(simd => |simd| { + let mut json_string = "{".to_owned(); + json_string += &" ".repeat(128); + json_string += "}"; + let input = BorrowedBytes::new(json_string.as_bytes()); + let iter = input.iter_blocks(&EmptyRecorder); + let quotes = simd.classify_quoted_sequences(iter); + let offset = input.leading_padding_len(); + + let mut classifier = simd.classify_structural_characters(quotes); + + assert_eq!(Some(Opening(Curly, offset)), classifier.next().unwrap()); + + let resume_state = classifier.stop(); + let mut resumed_classifier = simd.resume_structural_classification(resume_state); + + assert_eq!(Some(Closing(Curly, 129 + offset)), resumed_classifier.next().unwrap()); }); } } diff --git a/crates/rsonpath-lib/src/classification/structural/shared.rs b/crates/rsonpath-lib/src/classification/structural/shared.rs index 301a597a..459489b8 100644 --- a/crates/rsonpath-lib/src/classification/structural/shared.rs +++ b/crates/rsonpath-lib/src/classification/structural/shared.rs @@ -221,7 +221,7 @@ macro_rules! structural_classifier { let block = state.block.map(|b| { // SAFETY: target_feature invariant let mut block = unsafe { classifier.classify(b.block) }; - let idx_mask = <$mask_ty>::MAX << b.idx; + let idx_mask = <$mask_ty>::MAX.checked_shl(b.idx as u32).unwrap_or(0); block.structural_mask &= idx_mask; block diff --git a/crates/rsonpath-lib/src/engine/empty_query.rs b/crates/rsonpath-lib/src/engine/empty_query.rs index 85e41f2d..c363295d 100644 --- a/crates/rsonpath-lib/src/engine/empty_query.rs +++ b/crates/rsonpath-lib/src/engine/empty_query.rs @@ -6,9 +6,10 @@ //! here. use crate::{ engine::{error::EngineError, Input}, + input::{error::InputErrorConvertible, InputBlockIterator}, is_json_whitespace, result::{empty::EmptyRecorder, Match, MatchCount, MatchIndex, MatchSpan, Sink}, - FallibleIterator, BLOCK_SIZE, + BLOCK_SIZE, }; /// Count for an empty query – determine if the root exists. @@ -18,7 +19,7 @@ where { // Assuming a correct JSON, there is either one root if any non-whitespace character // occurs in the document, or the document is empty. - if input.seek_non_whitespace_forward(0)?.is_some() { + if input.seek_non_whitespace_forward(0).e()?.is_some() { Ok(1) } else { Ok(0) @@ -32,8 +33,8 @@ where S: Sink, { // Assuming a correct JSON, the root starts at the first non-whitespace character, if any. - if let Some((first_idx, _)) = input.seek_non_whitespace_forward(0)? { - sink.add_match(first_idx) + if let Some((first_idx, _)) = input.seek_non_whitespace_forward(0).e()? { + sink.add_match(first_idx - input.leading_padding_len()) .map_err(|err| EngineError::SinkError(Box::new(err)))?; } @@ -53,7 +54,7 @@ where // Some input know their lengths: bytes already in memory, file mmaps, etc. // A BufferedInput over an arbitrary Read stream cannot know its length, so we actually // need to iterate until the end and count the bytes. - if let Some((first_idx, _)) = input.seek_non_whitespace_forward(0)? { + if let Some((first_idx, _)) = input.seek_non_whitespace_forward(0).e()? { let end_idx = match input.len_hint() { Some(end_idx) => end_idx, // Known length, just take it. None => { @@ -61,7 +62,7 @@ where let mut iter = input.iter_blocks::<_, BLOCK_SIZE>(&EmptyRecorder); let mut end_idx = 0; - while (iter.next()?).is_some() { + while (iter.next().e()?).is_some() { end_idx += BLOCK_SIZE; } @@ -69,8 +70,11 @@ where } }; - sink.add_match(MatchSpan::from_indices(first_idx, end_idx)) - .map_err(|err| EngineError::SinkError(Box::new(err)))?; + sink.add_match(MatchSpan::from_indices( + first_idx - input.leading_padding_len(), + end_idx - input.leading_padding_len(), + )) + .map_err(|err| EngineError::SinkError(Box::new(err)))?; } Ok(()) @@ -88,8 +92,9 @@ where let mut iter = input.iter_blocks::<_, BLOCK_SIZE>(&EmptyRecorder); let mut res: Vec = vec![]; let mut first_significant_idx = None; + let mut offset = 0; - while let Some(block) = iter.next()? { + while let Some(block) = iter.next().e()? { if first_significant_idx.is_none() { // Start of the root not found yet, look for it. first_significant_idx = block.iter().position(|&x| !is_json_whitespace(x)); @@ -97,6 +102,8 @@ where if let Some(first_idx) = first_significant_idx { // Start of the root found in this block, copy the relevant part. res.extend(&block[first_idx..]); + } else { + offset += block.len(); } } else { // Start of the root was already found, now we are copying everything. @@ -110,7 +117,8 @@ where res.pop(); } - sink.add_match(Match::from_start_and_bytes(start, res)) + let actual_start = start + offset - input.leading_padding_len(); + sink.add_match(Match::from_start_and_bytes(actual_start, res)) .map_err(|err| EngineError::SinkError(Box::new(err)))?; } diff --git a/crates/rsonpath-lib/src/engine/head_skipping.rs b/crates/rsonpath-lib/src/engine/head_skipping.rs index 132989c1..425d30f4 100644 --- a/crates/rsonpath-lib/src/engine/head_skipping.rs +++ b/crates/rsonpath-lib/src/engine/head_skipping.rs @@ -13,7 +13,10 @@ use crate::{ debug, depth::Depth, engine::EngineError, - input::{error::InputError, Input, InputBlockIterator}, + input::{ + error::{InputError, InputErrorConvertible}, + Input, InputBlockIterator, + }, query::{ automaton::{Automaton, State}, JsonString, @@ -46,8 +49,8 @@ where &mut self, next_event: Structural, state: State, - structural_classifier: V::StructuralClassifier<'i, I::BlockIterator<'i, 'r, BLOCK_SIZE, R>>, - ) -> Result, V, MaskType>, EngineError>; + structural_classifier: V::StructuralClassifier<'i, I::BlockIterator<'i, 'r, R, BLOCK_SIZE>>, + ) -> Result, V, MaskType>, EngineError>; fn recorder(&mut self) -> &'r R; } @@ -148,12 +151,12 @@ impl<'b, 'q, I: Input, V: Simd> HeadSkip<'b, 'q, I, V, BLOCK_SIZE> { debug!("Needle found at {idx}"); let seek_start_idx = idx + head_skip.member_name.bytes_with_quotes().len(); - match head_skip.bytes.seek_non_whitespace_forward(seek_start_idx)? { - Some((colon_idx, b':')) => { - let (next_idx, next_c) = head_skip - .bytes - .seek_non_whitespace_forward(colon_idx + 1)? - .ok_or(EngineError::MissingItem())?; + match head_skip.bytes.seek_non_whitespace_forward(seek_start_idx).e()? { + Some((colon_idx, b':')) => { + let (next_idx, next_c) = head_skip + .bytes + .seek_non_whitespace_forward(colon_idx + 1).e()? + .ok_or(EngineError::MissingItem())?; let ResumedQuoteClassifier { classifier: quote_classifier, diff --git a/crates/rsonpath-lib/src/engine/main.rs b/crates/rsonpath-lib/src/engine/main.rs index 7529be85..a662c275 100644 --- a/crates/rsonpath-lib/src/engine/main.rs +++ b/crates/rsonpath-lib/src/engine/main.rs @@ -19,6 +19,7 @@ use crate::{ tail_skipping::TailSkip, Compiler, Engine, Input, }, + input::error::InputErrorConvertible, query::{ automaton::{Automaton, State, TransitionLabel}, error::CompilerError, @@ -68,16 +69,15 @@ impl Engine for MainEngine<'_> { where I: Input, { - let recorder = CountRecorder::new(); - if self.automaton.is_empty_query() { return empty_query::count(input); } + let recorder = CountRecorder::new(); config_simd!(self.simd => |simd| { let executor = query_executor(&self.automaton, input, &recorder, simd); - executor.run()?; - }); + executor.run() + })?; Ok(recorder.into()) } @@ -88,16 +88,15 @@ impl Engine for MainEngine<'_> { I: Input, S: Sink, { - let recorder = IndexRecorder::new(sink); - if self.automaton.is_empty_query() { return empty_query::index(input, sink); } + let recorder = IndexRecorder::new(sink, input.leading_padding_len()); config_simd!(self.simd => |simd| { let executor = query_executor(&self.automaton, input, &recorder, simd); - executor.run()?; - }); + executor.run() + })?; Ok(()) } @@ -108,16 +107,15 @@ impl Engine for MainEngine<'_> { I: Input, S: Sink, { - let recorder = ApproxSpanRecorder::new(sink); - if self.automaton.is_empty_query() { return empty_query::approx_span(input, sink); } + let recorder = ApproxSpanRecorder::new(sink, input.leading_padding_len()); config_simd!(self.simd => |simd| { let executor = query_executor(&self.automaton, input, &recorder, simd); - executor.run()?; - }); + executor.run() + })?; Ok(()) } @@ -128,16 +126,15 @@ impl Engine for MainEngine<'_> { I: Input, S: Sink, { - let recorder = NodesRecorder::build_recorder(sink); - if self.automaton.is_empty_query() { return empty_query::match_(input, sink); } + let recorder = NodesRecorder::build_recorder(sink, input.leading_padding_len()); config_simd!(self.simd => |simd| { let executor = query_executor(&self.automaton, input, &recorder, simd); - executor.run()?; - }); + executor.run() + })?; Ok(()) } @@ -147,9 +144,9 @@ macro_rules! Classifier { () => { TailSkip< 'i, - I::BlockIterator<'i, 'r, BLOCK_SIZE, R>, - V::QuotesClassifier<'i, I::BlockIterator<'i, 'r, BLOCK_SIZE, R>>, - V::StructuralClassifier<'i, I::BlockIterator<'i, 'r, BLOCK_SIZE, R>>, + I::BlockIterator<'i, 'r, R, BLOCK_SIZE>, + V::QuotesClassifier<'i, I::BlockIterator<'i, 'r, R, BLOCK_SIZE>>, + V::StructuralClassifier<'i, I::BlockIterator<'i, 'r, R, BLOCK_SIZE>>, V, BLOCK_SIZE> }; @@ -273,9 +270,9 @@ where debug!("Reporting result somewhere after {start_idx} with hint {hint:?}"); let index = match hint { - NodeTypeHint::Complex(BracketType::Curly) => self.input.seek_forward(start_idx, [b'{'])?, - NodeTypeHint::Complex(BracketType::Square) => self.input.seek_forward(start_idx, [b'['])?, - NodeTypeHint::Atomic => self.input.seek_non_whitespace_forward(start_idx)?, + NodeTypeHint::Complex(BracketType::Curly) => self.input.seek_forward(start_idx, [b'{']).e()?, + NodeTypeHint::Complex(BracketType::Square) => self.input.seek_forward(start_idx, [b'[']).e()?, + NodeTypeHint::Atomic => self.input.seek_non_whitespace_forward(start_idx).e()?, } .map(|x| x.0); @@ -285,6 +282,7 @@ where } } + #[inline(always)] fn handle_colon( &mut self, #[allow(unused_variables)] classifier: &mut Classifier!(), @@ -292,7 +290,7 @@ where ) -> Result<(), EngineError> { debug!("Colon"); - let is_next_opening = if let Some((_, c)) = self.input.seek_non_whitespace_forward(idx + 1)? { + let is_next_opening = if let Some((_, c)) = self.input.seek_non_whitespace_forward(idx + 1).e()? { c == b'{' || c == b'[' } else { false @@ -342,9 +340,10 @@ where Ok(()) } + #[inline(always)] fn handle_comma(&mut self, _classifier: &mut Classifier!(), idx: usize) -> Result<(), EngineError> { self.recorder.record_value_terminator(idx, self.depth)?; - let is_next_opening = if let Some((_, c)) = self.input.seek_non_whitespace_forward(idx + 1)? { + let is_next_opening = if let Some((_, c)) = self.input.seek_non_whitespace_forward(idx + 1).e()? { c == b'{' || c == b'[' } else { false @@ -376,6 +375,7 @@ where Ok(()) } + #[inline(always)] fn handle_opening( &mut self, classifier: &mut Classifier!(), @@ -457,7 +457,7 @@ where is_fallback_accepting || self.automaton.has_first_array_index_transition_to_accepting(self.state); if wants_first_item { - let next = self.input.seek_non_whitespace_forward(idx + 1)?; + let next = self.input.seek_non_whitespace_forward(idx + 1).e()?; match next { Some((_, b'[' | b'{' | b']')) => (), // Complex value or empty list. @@ -485,6 +485,7 @@ where Ok(()) } + #[inline(always)] fn handle_closing(&mut self, classifier: &mut Classifier!(), idx: usize) -> Result<(), EngineError> { debug!("Closing, decreasing depth and popping stack."); @@ -565,6 +566,7 @@ where } } + #[inline(always)] fn is_match(&self, idx: usize, member_name: &JsonString) -> Result { let len = member_name.bytes_with_quotes().len(); @@ -578,7 +580,9 @@ where } let start_idx = closing_quote_idx + 1 - len; - Ok(self.input.is_member_match(start_idx, closing_quote_idx, member_name)) + self.input + .is_member_match(start_idx, closing_quote_idx + 1, member_name) + .map_err(|x| x.into().into()) } fn verify_subtree_closed(&self) -> Result<(), EngineError> { @@ -650,8 +654,8 @@ where &mut self, next_event: Structural, state: State, - structural_classifier: V::StructuralClassifier<'i, I::BlockIterator<'i, 'r, BLOCK_SIZE, R>>, - ) -> Result, V, MaskType>, EngineError> { + structural_classifier: V::StructuralClassifier<'i, I::BlockIterator<'i, 'r, R, BLOCK_SIZE>>, + ) -> Result, V, MaskType>, EngineError> { let mut classifier = TailSkip::new(structural_classifier, self.simd); self.state = state; diff --git a/crates/rsonpath-lib/src/engine/tail_skipping.rs b/crates/rsonpath-lib/src/engine/tail_skipping.rs index ef381760..4f319920 100644 --- a/crates/rsonpath-lib/src/engine/tail_skipping.rs +++ b/crates/rsonpath-lib/src/engine/tail_skipping.rs @@ -51,7 +51,7 @@ where tail_skip.classifier = Some('a: { let resume_state = classifier.stop(); let DepthIteratorResumeOutcome(first_vector, mut depth_classifier) = - tail_skip.simd.resume_depth_classification(resume_state, opening); + tail_skip.simd.resume_depth_classification(resume_state, opening); let mut current_vector = match first_vector { Some(v) => Some(v), diff --git a/crates/rsonpath-lib/src/input.rs b/crates/rsonpath-lib/src/input.rs index 829969a6..9d4be971 100644 --- a/crates/rsonpath-lib/src/input.rs +++ b/crates/rsonpath-lib/src/input.rs @@ -6,25 +6,27 @@ //! documentation of each type to determine which to use. Here's a quick //! cheat-sheet: //! -//! | Input scenario | Type to use | -//! |:---------------|:---------------| -//! | file based | [`MmapInput`] | -//! | memory based | [`OwnedBytes`] | -//! | memory based, already aligned | [`BorrowedBytes`] | +//! | Input scenario | Type to use | +//! |:------------------------------|:------------------| +//! | memory based | [`BorrowedBytes`] | +//! | memory based, take ownership | [`OwnedBytes`] | +//! | file based | [`MmapInput`] | //! | [`Read`](std::io::Read) based | [`BufferedInput`] | -//! + pub mod borrowed; pub mod buffered; pub mod error; +pub mod mmap; pub mod owned; +mod padding; +mod slice; pub use borrowed::BorrowedBytes; pub use buffered::BufferedInput; -pub use owned::OwnedBytes; -pub mod mmap; pub use mmap::MmapInput; +pub use owned::OwnedBytes; use self::error::InputError; -use crate::{query::JsonString, result::InputRecorder, FallibleIterator}; +use crate::{query::JsonString, result::InputRecorder}; use std::ops::Deref; /// Make the struct repr(C) with alignment equal to [`MAX_BLOCK_SIZE`]. @@ -52,11 +54,19 @@ pub const MAX_BLOCK_SIZE: usize = 128; pub trait Input: Sized { /// Type of the iterator used by [`iter_blocks`](Input::iter_blocks), parameterized /// by the lifetime of source input and the size of the block. - type BlockIterator<'i, 'r, const N: usize, R>: InputBlockIterator<'i, N, Block = Self::Block<'i, N>> + type BlockIterator<'i, 'r, R, const N: usize>: InputBlockIterator< + 'i, + N, + Block = Self::Block<'i, N>, + Error = Self::Error, + > where Self: 'i, R: InputRecorder> + 'r; + /// Type of errors that can occur when operating on this [`Input`]. + type Error: Into; + /// Type of the blocks returned by the `BlockIterator`. type Block<'i, const N: usize>: InputBlock<'i, N> where @@ -73,10 +83,28 @@ pub trait Input: Sized { None } + /// Return the length of the padding added at the start of the input. + /// + /// This depends on the particular [`Input`] implementation, and may be zero. + /// In any case the length of the entire input should be equivalent to the length + /// of the source plus [`leading_padding_len`](`Input::leading_padding_len`) plus + /// [`trailing_padding_len`](`Input::trailing_padding_len`). + #[must_use] + fn leading_padding_len(&self) -> usize; + + /// Return the length of the padding added at the end of the input. + /// + /// This depends on the particular [`Input`] implementation, and may be zero. + /// In any case the length of the entire input should be equivalent to the length + /// of the source plus [`leading_padding_len`](`Input::leading_padding_len`) plus + /// [`trailing_padding_len`](`Input::trailing_padding_len`). + #[must_use] + fn trailing_padding_len(&self) -> usize; + /// Iterate over blocks of size `N` of the input. /// `N` has to be a power of two larger than 1. #[must_use] - fn iter_blocks<'i, 'r, R, const N: usize>(&'i self, recorder: &'r R) -> Self::BlockIterator<'i, 'r, N, R> + fn iter_blocks<'i, 'r, R, const N: usize>(&'i self, recorder: &'r R) -> Self::BlockIterator<'i, 'r, R, N> where R: InputRecorder>; @@ -93,7 +121,7 @@ pub trait Input: Sized { /// # Errors /// This function can read more data from the input if no relevant characters are found /// in the current buffer, which can fail. - fn seek_forward(&self, from: usize, needles: [u8; N]) -> Result, InputError>; + fn seek_forward(&self, from: usize, needles: [u8; N]) -> Result, Self::Error>; /// Search for the first byte in the input that is not ASCII whitespace /// starting from `from`. Returns a pair: the index of first such byte, @@ -103,7 +131,7 @@ pub trait Input: Sized { /// # Errors /// This function can read more data from the input if no relevant characters are found /// in the current buffer, which can fail. - fn seek_non_whitespace_forward(&self, from: usize) -> Result, InputError>; + fn seek_non_whitespace_forward(&self, from: usize) -> Result, Self::Error>; /// Search for the first byte in the input that is not ASCII whitespace /// starting from `from` and looking back. Returns a pair: @@ -118,17 +146,29 @@ pub trait Input: Sized { /// /// This will also check if the leading double quote is not /// escaped by a backslash character. - #[must_use] - fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> bool; + /// + /// # Errors + /// This function can read more data from the input if `to` falls beyond + /// the range that was already read, and the read operation can fail. + fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> Result; } /// An iterator over blocks of input of size `N`. /// Implementations MUST guarantee that the blocks returned from `next` /// are *exactly* of size `N`. -pub trait InputBlockIterator<'i, const N: usize>: FallibleIterator { +pub trait InputBlockIterator<'i, const N: usize> { /// The type of blocks returned. type Block: InputBlock<'i, N>; + /// Type of errors that can occur when reading from this iterator. + type Error: Into; + + /// Advances the iterator and returns the next value. + /// + /// # Errors + /// May fail depending on the implementation. + fn next(&mut self) -> Result, Self::Error>; + /// Get the offset of the iterator in the input. /// /// The offset is the starting point of the block that will be returned next @@ -169,420 +209,84 @@ impl<'i, const N: usize> InputBlock<'i, N> for &'i [u8] { } } -struct LastBlock { - bytes: [u8; MAX_BLOCK_SIZE], - absolute_start: usize, -} - -pub(super) mod in_slice { - use super::{LastBlock, MAX_BLOCK_SIZE}; - use crate::{query::JsonString, JSON_SPACE_BYTE}; - - #[inline] - pub(super) fn pad_last_block(bytes: &[u8]) -> LastBlock { - let mut last_block_buf = [JSON_SPACE_BYTE; MAX_BLOCK_SIZE]; - let last_block_start = (bytes.len() / MAX_BLOCK_SIZE) * MAX_BLOCK_SIZE; - let last_block_slice = &bytes[last_block_start..]; - - last_block_buf[..last_block_slice.len()].copy_from_slice(last_block_slice); - - LastBlock { - bytes: last_block_buf, - absolute_start: last_block_start, - } - } - - #[inline] - pub(super) fn seek_backward(bytes: &[u8], from: usize, needle: u8) -> Option { - let mut idx = from; - assert!(idx < bytes.len()); - - loop { - if bytes[idx] == needle { - return Some(idx); - } - if idx == 0 { - return None; - } - idx -= 1; - } - } - - #[inline] - pub(super) fn seek_forward(bytes: &[u8], from: usize, needles: [u8; N]) -> Option<(usize, u8)> { - assert!(N > 0); - let mut idx = from; - - if idx >= bytes.len() { - return None; - } - - loop { - let b = bytes[idx]; - if needles.contains(&b) { - return Some((idx, b)); - } - idx += 1; - if idx == bytes.len() { - return None; - } - } - } - - #[inline] - pub(super) fn seek_non_whitespace_forward(bytes: &[u8], from: usize) -> Option<(usize, u8)> { - let mut idx = from; - - if idx >= bytes.len() { - return None; - } +pub(super) trait SliceSeekable { + fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> bool; - loop { - let b = bytes[idx]; - if !b.is_ascii_whitespace() { - return Some((idx, b)); - } - idx += 1; - if idx == bytes.len() { - return None; - } - } - } + fn seek_backward(&self, from: usize, needle: u8) -> Option; - #[inline] - pub(super) fn seek_non_whitespace_backward(bytes: &[u8], from: usize) -> Option<(usize, u8)> { - let mut idx = from; + fn seek_forward(&self, from: usize, needles: [u8; N]) -> Option<(usize, u8)>; - if idx >= bytes.len() { - return None; - } + fn seek_non_whitespace_forward(&self, from: usize) -> Option<(usize, u8)>; - loop { - let b = bytes[idx]; - if !b.is_ascii_whitespace() { - return Some((idx, b)); - } - if idx == 0 { - return None; - } - idx -= 1; - } - } + fn seek_non_whitespace_backward(&self, from: usize) -> Option<(usize, u8)>; +} - #[inline] - pub(super) fn is_member_match(bytes: &[u8], from: usize, to: usize, member: &JsonString) -> bool { - if to >= bytes.len() { - return false; - } - let slice = &bytes[from..to + 1]; - member.bytes_with_quotes() == slice && (from == 0 || bytes[from - 1] != b'\\') +// This is mostly adapted from [slice::align_to](https://doc.rust-lang.org/std/primitive.slice.html#method.align_to). +fn align_to(bytes: &[u8]) -> (&[u8], &[u8], &[u8]) { + let ptr = bytes.as_ptr(); + let offset = ptr.align_offset(N); + if offset > bytes.len() { + (bytes, &[], &[]) + } else { + let (left, rest) = bytes.split_at(offset); + let middle_len = (rest.len() / N) * N; + let (middle, right) = rest.split_at(middle_len); + + (left, middle, right) } } #[cfg(test)] mod tests { - use super::{in_slice, MAX_BLOCK_SIZE}; - - mod input_block_impl_for_slice { - use pretty_assertions::assert_eq; - - #[test] - fn halves_splits_in_half() { - use super::super::InputBlock; - - let bytes = r#"0123456789abcdef"#.as_bytes(); - - let (half1, half2) = <&[u8] as InputBlock<16>>::halves(&bytes); - - assert_eq!(half1, "01234567".as_bytes()); - assert_eq!(half2, "89abcdef".as_bytes()); - } - } - - mod pad_last_block { - use crate::JSON_SPACE_BYTE; - - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn on_empty_bytes_is_all_whitespace() { - let result = in_slice::pad_last_block(&[]); - - assert_eq!(result.absolute_start, 0); - assert_eq!(result.bytes, [JSON_SPACE_BYTE; MAX_BLOCK_SIZE]); - } - - #[test] - fn on_bytes_smaller_than_full_block_gives_entire_block() { - let bytes = r#"{"test":42}"#.as_bytes(); - - let result = in_slice::pad_last_block(bytes); - - assert_eq!(result.absolute_start, 0); - assert_eq!(&result.bytes[0..11], bytes); - assert_eq!(&result.bytes[11..], [JSON_SPACE_BYTE; MAX_BLOCK_SIZE - 11]); - } - - #[test] - fn on_bytes_equal_to_full_block_gives_all_whitespace() { - let bytes = [42; MAX_BLOCK_SIZE]; - - let result = in_slice::pad_last_block(&bytes); - - assert_eq!(result.absolute_start, MAX_BLOCK_SIZE); - assert_eq!(result.bytes, [JSON_SPACE_BYTE; MAX_BLOCK_SIZE]); - } - - #[test] - fn on_bytes_longer_than_full_block_gives_last_fragment_padded() { - let mut bytes = [42; 2 * MAX_BLOCK_SIZE + 77]; - bytes[2 * MAX_BLOCK_SIZE..].fill(69); - - let result = in_slice::pad_last_block(&bytes); - - assert_eq!(result.absolute_start, 2 * MAX_BLOCK_SIZE); - assert_eq!(result.bytes[0..77], [69; 77]); - assert_eq!(result.bytes[77..], [JSON_SPACE_BYTE; MAX_BLOCK_SIZE - 77]); - } - } - - mod seek_backward { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn seeking_from_before_first_occurrence_returns_none() { - let bytes = r#"{"seek":42}"#.as_bytes(); - - let result = in_slice::seek_backward(bytes, 6, b':'); - - assert_eq!(result, None); - } - - #[test] - fn seeking_from_after_two_occurrences_returns_the_second_one() { - let bytes = r#"{"seek":42,"find":37}"#.as_bytes(); + use super::align_to; + use crate::input::MAX_BLOCK_SIZE; - let result = in_slice::seek_backward(bytes, bytes.len() - 1, b':'); + // Run all tests for the actual alignment we use. + const N: usize = MAX_BLOCK_SIZE; + const SIZE: usize = 1024; - assert_eq!(result, Some(17)); - } + #[repr(align(128))] + struct Aligned { + bytes: [u8; SIZE], } - mod seek_forward_1 { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn in_empty_slice_returns_none() { - let bytes = []; - - let result = in_slice::seek_forward(&bytes, 0, [0]); - - assert_eq!(result, None); - } - - #[test] - fn seeking_from_needle_returns_that() { - let bytes = r#"{"seek": 42}"#.as_bytes(); - - let result = in_slice::seek_forward(bytes, 7, [b':']); - - assert_eq!(result, Some((7, b':'))); - } - - #[test] - fn seeking_from_not_needle_returns_next_needle() { - let bytes = "seek: \t\n42}".as_bytes(); - - let result = in_slice::seek_forward(bytes, 5, [b'2']); - - assert_eq!(result, Some((9, b'2'))); - } - - #[test] - fn seeking_from_not_needle_when_there_is_no_needle_returns_none() { - let bytes = "seek: \t\n42}".as_bytes(); - - let result = in_slice::seek_forward(bytes, 5, [b'3']); + #[test] + fn test_all_alignments() { + // We construct a byte array that is already aligned, + // and then take all suffixes for all possible misalignments + // and small sizes. + let aligned = Aligned { bytes: get_bytes() }; + let slice = &aligned.bytes; - assert_eq!(result, None); + for i in 0..slice.len() { + let misalignment = i % N; + test_with_misalignment(misalignment, &slice[i..]); } } - mod seek_forward_2 { + fn get_bytes() -> [u8; SIZE] { + let mut bytes = [0; SIZE]; - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn in_empty_slice_returns_none() { - let bytes = []; - - let result = in_slice::seek_forward(&bytes, 0, [0, 1]); - - assert_eq!(result, None); + for (i, b) in bytes.iter_mut().enumerate() { + let x = i % (u8::MAX as usize); + *b = x as u8; } - #[test] - fn seeking_from_needle_1_returns_that() { - let bytes = r#"{"seek": 42}"#.as_bytes(); - - let result = in_slice::seek_forward(bytes, 7, [b':', b'4']); - - assert_eq!(result, Some((7, b':'))); - } - - #[test] - fn seeking_from_needle_2_returns_that() { - let bytes = r#"{"seek": 42}"#.as_bytes(); - - let result = in_slice::seek_forward(bytes, 7, [b'4', b':']); - - assert_eq!(result, Some((7, b':'))); - } - - #[test] - fn seeking_from_not_needle_when_next_is_needle_1_returns_that() { - let bytes = "seek: \t\n42}".as_bytes(); - - let result = in_slice::seek_forward(bytes, 5, [b'4', b'2']); - - assert_eq!(result, Some((8, b'4'))); - } - - #[test] - fn seeking_from_not_needle_when_next_is_needle_2_returns_that() { - let bytes = "seek: \t\n42}".as_bytes(); - - let result = in_slice::seek_forward(bytes, 5, [b'2', b'4']); - - assert_eq!(result, Some((8, b'4'))); - } - - #[test] - fn seeking_from_not_needle_when_there_is_no_needle_returns_none() { - let bytes = "seek: \t\n42}".as_bytes(); - - let result = in_slice::seek_forward(bytes, 5, [b'3', b'0']); - - assert_eq!(result, None); - } + bytes } - mod seek_non_whitespace_forward { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn in_empty_slice_returns_none() { - let bytes = []; - - let result = in_slice::seek_non_whitespace_forward(&bytes, 0); - - assert_eq!(result, None); - } + fn test_with_misalignment(misalignment: usize, slice: &[u8]) { + let expected_left_len = (N - misalignment) % N; + let expected_rem_len = slice.len() - expected_left_len; + let expected_middle_len = (expected_rem_len / N) * N; + let expected_right_len = expected_rem_len - expected_middle_len; - #[test] - fn seeking_from_non_whitespace_returns_that() { - let bytes = r#"{"seek": 42}"#.as_bytes(); + let (left, middle, right) = align_to::(slice); + let glued_back: Vec<_> = [left, middle, right].into_iter().flatten().copied().collect(); - let result = in_slice::seek_non_whitespace_forward(bytes, 7); - - assert_eq!(result, Some((7, b':'))); - } - - #[test] - fn seeking_from_whitespace_returns_next_non_whitespace() { - let bytes = "seek: \t\n42}".as_bytes(); - - let result = in_slice::seek_non_whitespace_forward(bytes, 5); - - assert_eq!(result, Some((8, b'4'))); - } - - #[test] - fn seeking_from_whitespace_when_there_is_no_more_non_whitespace_returns_none() { - let bytes = "seek: \t\n ".as_bytes(); - - let result = in_slice::seek_non_whitespace_forward(bytes, 5); - - assert_eq!(result, None); - } - } - - mod seek_non_whitespace_backward { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn in_empty_slice_returns_none() { - let bytes = []; - - let result = in_slice::seek_non_whitespace_backward(&bytes, 0); - - assert_eq!(result, None); - } - - #[test] - fn seeking_from_non_whitespace_returns_that() { - let bytes = r#"{"seek": 42}"#.as_bytes(); - - let result = in_slice::seek_non_whitespace_backward(bytes, 7); - - assert_eq!(result, Some((7, b':'))); - } - - #[test] - fn seeking_from_whitespace_returns_previous_non_whitespace() { - let bytes = "seek: \t\n42}".as_bytes(); - - let result = in_slice::seek_non_whitespace_backward(bytes, 7); - - assert_eq!(result, Some((4, b':'))); - } - } - - mod is_member_match { - use super::*; - use crate::query::JsonString; - use pretty_assertions::assert_eq; - - #[test] - fn on_exact_match_returns_true() { - let bytes = r#"{"needle":42,"other":37}"#.as_bytes(); - - let result = in_slice::is_member_match(bytes, 1, 8, &JsonString::new("needle")); - - assert_eq!(result, true); - } - - #[test] - fn matching_without_double_quotes_returns_false() { - let bytes = r#"{"needle":42,"other":37}"#.as_bytes(); - - let result = in_slice::is_member_match(bytes, 2, 7, &JsonString::new("needle")); - - assert_eq!(result, false); - } - - #[test] - fn when_match_is_partial_due_to_escaped_double_quote_returns_false() { - let bytes = r#"{"fake\"needle":42,"other":37}"#.as_bytes(); - - let result = in_slice::is_member_match(bytes, 7, 14, &JsonString::new("needle")); - - assert_eq!(result, false); - } - - #[test] - fn when_looking_for_string_with_escaped_double_quote_returns_true() { - let bytes = r#"{"fake\"needle":42,"other":37}"#.as_bytes(); - - let result = in_slice::is_member_match(bytes, 1, 14, &JsonString::new(r#"fake\"needle"#)); - - assert_eq!(result, true); - } + assert_eq!(left.len(), expected_left_len, "misalignment = {misalignment}"); + assert_eq!(middle.len(), expected_middle_len, "misalignment = {misalignment}"); + assert_eq!(right.len(), expected_right_len, "misalignment = {misalignment}"); + assert_eq!(glued_back, slice, "misalignment = {misalignment}"); } } diff --git a/crates/rsonpath-lib/src/input/borrowed.rs b/crates/rsonpath-lib/src/input/borrowed.rs index 2e2a5bb1..25e50a57 100644 --- a/crates/rsonpath-lib/src/input/borrowed.rs +++ b/crates/rsonpath-lib/src/input/borrowed.rs @@ -2,26 +2,34 @@ //! //! Choose this implementation if: //! -//! 1. You already have the data loaded in-memory and it is properly aligned. +//! 1. You already have the data loaded in-memory and can borrow it while +//! using the engine. //! //! ## Performance characteristics //! //! This type of input is the fastest to process for the engine, //! since there is no additional overhead from loading anything to memory. +//! It is on par with [`OwnedBytes`](`super::OwnedBytes`), but doesn't take ownership +//! of the bytes. -use super::*; +use super::{ + align_to, + error::Infallible, + padding::{EndPaddedInput, PaddedBlock, TwoSidesPaddedInput}, + Input, InputBlockIterator, SliceSeekable, MAX_BLOCK_SIZE, +}; use crate::{debug, query::JsonString, result::InputRecorder}; /// Input wrapping a borrowed [`[u8]`] buffer. pub struct BorrowedBytes<'a> { - bytes: &'a [u8], - last_block: LastBlock, + middle_bytes: &'a [u8], + first_block: PaddedBlock, + last_block: PaddedBlock, } /// Iterator over blocks of [`BorrowedBytes`] of size exactly `N`. -pub struct BorrowedBytesBlockIterator<'a, 'r, const N: usize, R> { - input: &'a [u8], - last_block: &'a LastBlock, +pub struct BorrowedBytesBlockIterator<'r, I, R, const N: usize> { + input: I, idx: usize, recorder: &'r R, } @@ -29,153 +37,298 @@ pub struct BorrowedBytesBlockIterator<'a, 'r, const N: usize, R> { impl<'a> BorrowedBytes<'a> { /// Create a new instance of [`BorrowedBytes`] wrapping the given buffer. /// - /// # Safety - /// The buffer must satisfy all invariants of [`BorrowedBytes`], - /// since it is not copied or modified. It must: - /// - have length divisible by [`MAX_BLOCK_SIZE`] (the function checks this); - /// - be aligned to [`MAX_BLOCK_SIZE`]. - /// - /// The latter condition cannot be reliably checked. - /// Violating it may result in memory errors where the engine relies - /// on proper alignment. - /// - /// # Panics - /// - /// If `bytes.len()` is not divisible by [`MAX_BLOCK_SIZE`]. + /// The input will be automatically padded internally, incurring at most + /// two times [`MAX_BLOCK_SIZE`] of memory overhead. #[must_use] #[inline(always)] - pub unsafe fn new(bytes: &'a [u8]) -> Self { - assert_eq!(bytes.len() % MAX_BLOCK_SIZE, 0); - let last_block = in_slice::pad_last_block(bytes); - Self { bytes, last_block } + pub fn new(bytes: &'a [u8]) -> Self { + let (first, middle, last) = align_to::(bytes); + let first_block = PaddedBlock::pad_first_block(first); + let last_block = PaddedBlock::pad_last_block(last); + + Self { + middle_bytes: middle, + first_block, + last_block, + } } - /// Get a reference to the bytes as a slice. - #[must_use] - #[inline(always)] - pub fn as_slice(&self) -> &[u8] { - self.bytes + pub(super) fn as_padded_input(&self) -> TwoSidesPaddedInput { + TwoSidesPaddedInput::new(&self.first_block, self.middle_bytes, &self.last_block) } +} - /// Copy the bytes to an [`OwnedBytes`] instance. - #[must_use] +impl<'a> From<&'a [u8]> for BorrowedBytes<'a> { #[inline(always)] - pub fn to_owned(&self) -> OwnedBytes { - OwnedBytes::from(self) + fn from(value: &'a [u8]) -> Self { + BorrowedBytes::new(value) } } -impl<'a> AsRef<[u8]> for BorrowedBytes<'a> { +impl<'a> From<&'a str> for BorrowedBytes<'a> { #[inline(always)] - fn as_ref(&self) -> &[u8] { - self.bytes + fn from(value: &'a str) -> Self { + BorrowedBytes::new(value.as_bytes()) } } -impl<'a, 'r, const N: usize, R> BorrowedBytesBlockIterator<'a, 'r, N, R> +impl<'a, 'r, I, R, const N: usize> BorrowedBytesBlockIterator<'r, I, R, N> where R: InputRecorder<&'a [u8]>, { #[must_use] #[inline(always)] - pub(super) fn new(bytes: &'a [u8], last_block: &'a LastBlock, recorder: &'r R) -> Self { + pub(super) fn new(input: I, recorder: &'r R) -> Self { Self { - input: bytes, idx: 0, - last_block, + input, recorder, } } } impl<'a> Input for BorrowedBytes<'a> { - type BlockIterator<'b, 'r, const N: usize, R> = BorrowedBytesBlockIterator<'b, 'r, N, R> + type BlockIterator<'b, 'r, R, const N: usize> = BorrowedBytesBlockIterator<'r, TwoSidesPaddedInput<'b>, R, N> where Self: 'b, R: InputRecorder<&'b [u8]> + 'r; + type Error = Infallible; type Block<'b, const N: usize> = &'b [u8] where Self: 'b; + #[inline(always)] + fn leading_padding_len(&self) -> usize { + self.first_block.padding_len() + } + + #[inline(always)] + fn trailing_padding_len(&self) -> usize { + self.last_block.padding_len() + } + #[inline(always)] fn len_hint(&self) -> Option { - Some((self.bytes.len() / MAX_BLOCK_SIZE + 1) * MAX_BLOCK_SIZE) + Some(self.middle_bytes.len() + self.first_block.len() + self.last_block.len()) } #[inline(always)] - fn iter_blocks<'b, 'r, R, const N: usize>(&'b self, recorder: &'r R) -> Self::BlockIterator<'b, 'r, N, R> + fn iter_blocks<'b, 'r, R, const N: usize>(&'b self, recorder: &'r R) -> Self::BlockIterator<'b, 'r, R, N> where R: InputRecorder<&'b [u8]>, { + let padded_input = TwoSidesPaddedInput::new(&self.first_block, self.middle_bytes, &self.last_block); + Self::BlockIterator { - input: self.bytes, idx: 0, - last_block: &self.last_block, + input: padded_input, recorder, } } #[inline] fn seek_backward(&self, from: usize, needle: u8) -> Option { - in_slice::seek_backward(self.bytes, from, needle) + return if from >= MAX_BLOCK_SIZE && from < self.middle_bytes.len() + MAX_BLOCK_SIZE { + match self.middle_bytes.seek_backward(from - MAX_BLOCK_SIZE, needle) { + Some(x) => Some(x + MAX_BLOCK_SIZE), + None => handle_first(&self.first_block, needle), + } + } else { + self.as_padded_input().seek_backward(from, needle) + }; + + #[cold] + #[inline(never)] + fn handle_first(first_block: &PaddedBlock, needle: u8) -> Option { + first_block.bytes().seek_backward(first_block.len() - 1, needle) + } } #[inline] - fn seek_forward(&self, from: usize, needles: [u8; N]) -> Result, InputError> { - Ok(in_slice::seek_forward(self.as_slice(), from, needles)) + fn seek_forward(&self, from: usize, needles: [u8; N]) -> Result, Infallible> { + return Ok( + if from >= MAX_BLOCK_SIZE && from < self.middle_bytes.len() + MAX_BLOCK_SIZE { + match self.middle_bytes.seek_forward(from - MAX_BLOCK_SIZE, needles) { + Some((x, y)) => Some((x + MAX_BLOCK_SIZE, y)), + None => handle_last(&self.last_block, MAX_BLOCK_SIZE + self.middle_bytes.len(), needles), + } + } else { + self.as_padded_input().seek_forward(from, needles) + }, + ); + + #[cold] + #[inline(never)] + fn handle_last( + last_block: &PaddedBlock, + offset: usize, + needles: [u8; N], + ) -> Option<(usize, u8)> { + last_block + .bytes() + .seek_forward(0, needles) + .map(|(x, y)| (x + offset, y)) + } } #[inline] - fn seek_non_whitespace_forward(&self, from: usize) -> Result, InputError> { - Ok(in_slice::seek_non_whitespace_forward(self.bytes, from)) + fn seek_non_whitespace_forward(&self, from: usize) -> Result, Infallible> { + return Ok( + // The hot path is when we start and end within the middle section. + // We use the regular slice path for that scenario, and fall back to the very expensive + // TwoSidesPaddedInput with all bells and whistles only when that doesn't work. + if from >= MAX_BLOCK_SIZE && from < self.middle_bytes.len() + MAX_BLOCK_SIZE { + match self.middle_bytes.seek_non_whitespace_forward(from - MAX_BLOCK_SIZE) { + Some((x, y)) => Some((x + MAX_BLOCK_SIZE, y)), + None => handle_last(&self.last_block, MAX_BLOCK_SIZE + self.middle_bytes.len()), + } + } else { + self.as_padded_input().seek_non_whitespace_forward(from) + }, + ); + + #[cold] + #[inline(never)] + fn handle_last(last_block: &PaddedBlock, offset: usize) -> Option<(usize, u8)> { + last_block + .bytes() + .seek_non_whitespace_forward(0) + .map(|(x, y)| (x + offset, y)) + } } #[inline] fn seek_non_whitespace_backward(&self, from: usize) -> Option<(usize, u8)> { - in_slice::seek_non_whitespace_backward(self.bytes, from) + return if from >= MAX_BLOCK_SIZE && from < self.middle_bytes.len() + MAX_BLOCK_SIZE { + match self.middle_bytes.seek_non_whitespace_backward(from - MAX_BLOCK_SIZE) { + Some((x, y)) => Some((x + MAX_BLOCK_SIZE, y)), + None => handle_first(&self.first_block), + } + } else { + self.as_padded_input().seek_non_whitespace_backward(from) + }; + + #[cold] + #[inline(never)] + fn handle_first(first_block: &PaddedBlock) -> Option<(usize, u8)> { + first_block.bytes().seek_non_whitespace_backward(first_block.len() - 1) + } } - #[inline] - fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> bool { - in_slice::is_member_match(self.bytes, from, to, member) + #[inline(always)] + fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> Result { + debug_assert!(from < to); + // The hot path is when we're checking fully within the middle section. + // This has to be as fast as possible, so the "cold" path referring to the TwoSidesPaddedInput + // impl is explicitly marked with #[cold]. + if from > MAX_BLOCK_SIZE && to < self.middle_bytes.len() + MAX_BLOCK_SIZE { + // This is the hot path -- do the bounds check and memcmp. + let bytes = self.middle_bytes; + let from = from - MAX_BLOCK_SIZE; + let to = to - MAX_BLOCK_SIZE; + let slice = &bytes[from..to]; + Ok(member.bytes_with_quotes() == slice && (from == 0 || bytes[from - 1] != b'\\')) + } else { + // This is a very expensive, cold path. + Ok(self.as_padded_input().is_member_match(from, to, member)) + } } } -impl<'a, 'r, const N: usize, R> FallibleIterator for BorrowedBytesBlockIterator<'a, 'r, N, R> +impl<'a, 'r, R, const N: usize> InputBlockIterator<'a, N> + for BorrowedBytesBlockIterator<'r, TwoSidesPaddedInput<'a>, R, N> where - R: InputRecorder<&'a [u8]>, + R: InputRecorder<&'a [u8]> + 'r, { - type Item = &'a [u8]; - type Error = InputError; + type Block = &'a [u8]; + type Error = Infallible; - #[inline] - fn next(&mut self) -> Result, Self::Error> { + #[inline(always)] + fn next(&mut self) -> Result, Self::Error> { debug!("next!"); - - if self.idx >= self.input.len() { - Ok(None) - } else if self.idx >= self.last_block.absolute_start { - let i = self.idx - self.last_block.absolute_start; - self.idx += N; - let block = &self.last_block.bytes[i..i + N]; - + return if self.idx >= MAX_BLOCK_SIZE && self.idx < self.input.middle().len() + MAX_BLOCK_SIZE { + let start = self.idx - MAX_BLOCK_SIZE; + // SAFETY: Bounds check above. + // self.idx >= MBS => start >= 0, and self.idx < middle.len + MBS => self.idx < middle.len + // By construction, middle has length divisible by N. + let block = unsafe { self.input.middle().get_unchecked(start..start + N) }; self.recorder.record_block_start(block); - + self.idx += N; Ok(Some(block)) } else { - let block = &self.input[self.idx..self.idx + N]; - self.idx += N; + Ok(cold_path(self)) + }; - self.recorder.record_block_start(block); + #[cold] + fn cold_path<'a, 'r, R, const N: usize>( + iter: &mut BorrowedBytesBlockIterator<'r, TwoSidesPaddedInput<'a>, R, N>, + ) -> Option<&'a [u8]> + where + R: InputRecorder<&'a [u8]>, + { + let block = iter.input.try_slice(iter.idx, N); - Ok(Some(block)) + if let Some(b) = block { + iter.recorder.record_block_start(b); + iter.idx += N; + } + + block } } + + #[inline(always)] + fn offset(&mut self, count: isize) { + assert!(count >= 0); + debug!("offsetting input iter by {count}"); + self.idx += count as usize * N; + } + + #[inline(always)] + fn get_offset(&self) -> usize { + debug!("getting input iter {}", self.idx); + self.idx + } } -impl<'a, 'r, const N: usize, R> InputBlockIterator<'a, N> for BorrowedBytesBlockIterator<'a, 'r, N, R> +impl<'a, 'r, R, const N: usize> InputBlockIterator<'a, N> for BorrowedBytesBlockIterator<'r, EndPaddedInput<'a>, R, N> where R: InputRecorder<&'a [u8]> + 'r, { type Block = &'a [u8]; + type Error = Infallible; + + #[inline(always)] + fn next(&mut self) -> Result, Self::Error> { + debug!("next!"); + return if self.idx < self.input.middle().len() { + let start = self.idx; + // SAFETY: Bounds check above. + // self.idx >= MBS => start >= 0, and self.idx < middle.len + MBS => self.idx < middle.len + // By construction, middle has length divisible by N. + let block = unsafe { self.input.middle().get_unchecked(start..start + N) }; + self.recorder.record_block_start(block); + self.idx += N; + Ok(Some(block)) + } else { + Ok(cold_path(self)) + }; + + #[cold] + fn cold_path<'a, 'r, R, const N: usize>( + iter: &mut BorrowedBytesBlockIterator<'r, EndPaddedInput<'a>, R, N>, + ) -> Option<&'a [u8]> + where + R: InputRecorder<&'a [u8]>, + { + let block = iter.input.try_slice(iter.idx, N); + + if let Some(b) = block { + iter.recorder.record_block_start(b); + iter.idx += N; + } + + block + } + } #[inline(always)] fn offset(&mut self, count: isize) { diff --git a/crates/rsonpath-lib/src/input/buffered.rs b/crates/rsonpath-lib/src/input/buffered.rs index b4db3b00..1081268b 100644 --- a/crates/rsonpath-lib/src/input/buffered.rs +++ b/crates/rsonpath-lib/src/input/buffered.rs @@ -17,13 +17,15 @@ //! reallocating the internal buffers. use super::{ - error::InputError, in_slice, repr_align_block_size, Input, InputBlock, InputBlockIterator, MAX_BLOCK_SIZE, -}; -use crate::{ - error::InternalRsonpathError, query::JsonString, result::InputRecorder, FallibleIterator, JSON_SPACE_BYTE, + error::InputError, repr_align_block_size, Input, InputBlock, InputBlockIterator, SliceSeekable, MAX_BLOCK_SIZE, }; +use crate::{error::InternalRsonpathError, query::JsonString, result::InputRecorder, JSON_SPACE_BYTE}; use std::{cell::RefCell, io::Read, ops::Deref, slice}; +// The buffer has to be a multiple of MAX_BLOCK_SIZE. +// It could technically be as small as MAX_BLOCK_SIZE, but there is a performance consideration. +// The fewer reads we make, the smoother the pipeline of the engine can go. +// 8KB is too little and hurts performance. 64KB appears to be a good compromise. const BUF_SIZE: usize = 64 * 1024; static_assertions::const_assert!(BUF_SIZE >= MAX_BLOCK_SIZE); @@ -36,6 +38,7 @@ struct InternalBuffer { source: R, bytes: Vec, chunk_idx: usize, + source_read: usize, eof: bool, } @@ -91,6 +94,7 @@ impl InternalBuffer { } total += size; + self.source_read += size; } Ok(total > 0) @@ -106,6 +110,7 @@ impl BufferedInput { bytes: vec![], eof: false, chunk_idx: 0, + source_read: 0, })) } @@ -119,19 +124,36 @@ impl BufferedInput { bytes: Vec::with_capacity(blocks_needed), eof: false, chunk_idx: 0, + source_read: 0, })) } } impl Input for BufferedInput { - type BlockIterator<'a, 'r, const N: usize, IR> = BufferedInputBlockIterator<'a, 'r, R, IR, N> + type BlockIterator<'a, 'r, IR, const N: usize> = BufferedInputBlockIterator<'a, 'r, R, IR, N> where Self: 'a, IR: InputRecorder> + 'r; + type Error = InputError; type Block<'a, const N: usize> = BufferedInputBlock where Self: 'a; #[inline(always)] - fn iter_blocks<'i, 'r, IR, const N: usize>(&'i self, recorder: &'r IR) -> Self::BlockIterator<'i, 'r, N, IR> + fn leading_padding_len(&self) -> usize { + 0 + } + + #[inline(always)] + fn trailing_padding_len(&self) -> usize { + let rem = self.0.borrow().source_read % BUF_SIZE; + if rem == 0 { + 0 + } else { + BUF_SIZE - rem + } + } + + #[inline(always)] + fn iter_blocks<'i, 'r, IR, const N: usize>(&'i self, recorder: &'r IR) -> Self::BlockIterator<'i, 'r, IR, N> where IR: InputRecorder>, { @@ -145,8 +167,7 @@ impl Input for BufferedInput { #[inline(always)] fn seek_backward(&self, from: usize, needle: u8) -> Option { let buf = self.0.borrow(); - let slice = buf.as_slice(); - in_slice::seek_backward(slice, from, needle) + buf.as_slice().seek_backward(from, needle) } #[inline] @@ -155,10 +176,7 @@ impl Input for BufferedInput { let mut moving_from = from; loop { - let res = { - let slice = buf.as_slice(); - in_slice::seek_forward(slice, moving_from, needles) - }; + let res = buf.as_slice().seek_forward(moving_from, needles); moving_from = buf.len(); @@ -176,10 +194,7 @@ impl Input for BufferedInput { let mut moving_from = from; loop { - let res = { - let slice = buf.as_slice(); - in_slice::seek_non_whitespace_forward(slice, moving_from) - }; + let res = buf.as_slice().seek_non_whitespace_forward(moving_from); moving_from = buf.len(); @@ -194,30 +209,37 @@ impl Input for BufferedInput { #[inline(always)] fn seek_non_whitespace_backward(&self, from: usize) -> Option<(usize, u8)> { let buf = self.0.borrow(); - let slice = buf.as_slice(); - in_slice::seek_non_whitespace_backward(slice, from) + buf.as_slice().seek_non_whitespace_backward(from) } #[inline(always)] - fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> bool { - let buf = self.0.borrow(); - let slice = buf.as_slice(); - in_slice::is_member_match(slice, from, to, member) + fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> Result { + let mut buf = self.0.borrow_mut(); + + while buf.len() < to { + if !buf.read_more()? { + return Ok(false); + } + } + + let bytes = buf.as_slice(); + let slice = &bytes[from..to]; + Ok(member.bytes_with_quotes() == slice && (from == 0 || bytes[from - 1] != b'\\')) } } -impl<'a, 'r, R: Read, IR, const N: usize> FallibleIterator for BufferedInputBlockIterator<'a, 'r, R, IR, N> +impl<'a, 'r, R: Read, IR, const N: usize> InputBlockIterator<'a, N> for BufferedInputBlockIterator<'a, 'r, R, IR, N> where IR: InputRecorder>, { - type Item = BufferedInputBlock; + type Block = BufferedInputBlock; type Error = InputError; #[inline] - fn next(&mut self) -> Result, Self::Error> { + fn next(&mut self) -> Result, Self::Error> { let buf = self.input.0.borrow(); - if self.idx + N < buf.len() { + if self.idx + N <= buf.len() { let slice = &buf.as_slice()[self.idx..self.idx + N]; let block: [u8; N] = slice .try_into() @@ -239,13 +261,6 @@ where } } } -} - -impl<'a, 'r, R: Read, IR, const N: usize> InputBlockIterator<'a, N> for BufferedInputBlockIterator<'a, 'r, R, IR, N> -where - IR: InputRecorder>, -{ - type Block = BufferedInputBlock; #[inline(always)] fn offset(&mut self, count: isize) { diff --git a/crates/rsonpath-lib/src/input/error.rs b/crates/rsonpath-lib/src/input/error.rs index aa351b3d..e92fb10d 100644 --- a/crates/rsonpath-lib/src/input/error.rs +++ b/crates/rsonpath-lib/src/input/error.rs @@ -7,7 +7,7 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum InputError { /// Error that occurs when an unbounded-sized implementation - /// (e.g. [`OwnedBytes`](super::OwnedBytes)) would allocate more than the global limit of [isize::MAX]. + /// (e.g. [`BufferedInput`](super::BufferedInput)) would allocate more than the global limit of [`isize::MAX`]. #[error("owned buffer size exceeded the hard system limit of isize::MAX")] AllocationSizeExceeded, /// Error when reading input from an underlying IO handle. @@ -22,3 +22,44 @@ pub enum InputError { InternalRsonpathError, ), } + +/// Hack to convert errors to [`InputError`] easily. +/// +/// The bound on errors in [`Input`](super::Input) is [`Into`]. +/// This doesn't work with the usual `?` Rust operator, as that requires the reverse +/// bound (for [`InputError`] to be `From` the source). This is not easily expressible +/// as a bound on [`Input`](super::Input). Instead we use this small function to perform +/// the same conversion. +pub(crate) trait InputErrorConvertible: Sized { + /// Convert to [`InputError`] result. + /// + /// Instead of + /// ```rust,ignore + /// err.map_err(|x| x.into())?; + /// ``` + /// you can write + /// ```rust,ignore + /// err.e()?; + /// ``` + /// as a shorthand. + fn e(self) -> Result; +} + +impl> InputErrorConvertible for Result { + #[inline(always)] + fn e(self) -> Result { + self.map_err(std::convert::Into::into) + } +} + +/// Error type for [`Input`](`super::Input`) implementations that never fail +/// when reading more input. +#[derive(Debug, Error)] +pub enum Infallible {} + +impl From for InputError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + unreachable!() + } +} diff --git a/crates/rsonpath-lib/src/input/mmap.rs b/crates/rsonpath-lib/src/input/mmap.rs index 14e9216d..b52afbfb 100644 --- a/crates/rsonpath-lib/src/input/mmap.rs +++ b/crates/rsonpath-lib/src/input/mmap.rs @@ -15,14 +15,20 @@ //! by an order of magnitude to execute the query on a memory map than it is to simply read the //! file into main memory. -use super::{borrowed::BorrowedBytesBlockIterator, error::InputError, in_slice, Input, LastBlock, MAX_BLOCK_SIZE}; -use crate::{query::JsonString, result::InputRecorder}; +use super::{ + borrowed::BorrowedBytesBlockIterator, + error::{Infallible, InputError}, + padding::PaddedBlock, + Input, SliceSeekable, MAX_BLOCK_SIZE, +}; +use crate::{input::padding::EndPaddedInput, query::JsonString, result::InputRecorder}; use memmap2::{Mmap, MmapAsRawDesc}; /// Input wrapping a memory mapped file. pub struct MmapInput { mmap: Mmap, - last_block: LastBlock, + last_block_start: usize, + last_block: PaddedBlock, } impl MmapInput { @@ -40,56 +46,133 @@ impl MmapInput { pub unsafe fn map_file(file_desc: D) -> Result { match Mmap::map(file_desc) { Ok(mmap) => { - let last_block = in_slice::pad_last_block(&mmap); - Ok(Self { mmap, last_block }) + let last_block_start = (mmap.len() / MAX_BLOCK_SIZE) * MAX_BLOCK_SIZE; + let last_block = PaddedBlock::pad_last_block(&mmap[last_block_start..]); + Ok(Self { + mmap, + last_block_start, + last_block, + }) } Err(err) => Err(err.into()), } } + + pub(super) fn as_padded_input(&self) -> EndPaddedInput { + let middle = &self.mmap.as_ref()[..self.last_block_start]; + EndPaddedInput::new(middle, &self.last_block) + } } impl Input for MmapInput { - type BlockIterator<'a, 'r, const N: usize, R> = BorrowedBytesBlockIterator<'a, 'r, N, R> + type BlockIterator<'a, 'r, R, const N: usize> = BorrowedBytesBlockIterator<'r, EndPaddedInput<'a>, R, N> where R: InputRecorder<&'a [u8]> + 'r; + type Error = Infallible; type Block<'a, const N: usize> = &'a [u8]; + #[inline(always)] + fn leading_padding_len(&self) -> usize { + 0 + } + + #[inline(always)] + fn trailing_padding_len(&self) -> usize { + self.last_block.padding_len() + } + #[inline(always)] fn len_hint(&self) -> Option { Some((self.mmap.len() / MAX_BLOCK_SIZE + 1) * MAX_BLOCK_SIZE) } #[inline(always)] - fn iter_blocks<'a, 'r, R, const N: usize>(&'a self, recorder: &'r R) -> Self::BlockIterator<'a, 'r, N, R> + fn iter_blocks<'a, 'r, R, const N: usize>(&'a self, recorder: &'r R) -> Self::BlockIterator<'a, 'r, R, N> where R: InputRecorder<&'a [u8]>, { - BorrowedBytesBlockIterator::new(&self.mmap, &self.last_block, recorder) + let padded_input = EndPaddedInput::new(&self.mmap[..self.last_block_start], &self.last_block); + + BorrowedBytesBlockIterator::new(padded_input, recorder) } #[inline] fn seek_backward(&self, from: usize, needle: u8) -> Option { - in_slice::seek_backward(&self.mmap, from, needle) + return if from < self.last_block_start { + self.mmap.seek_backward(from, needle) + } else { + self.as_padded_input().seek_backward(from, needle) + }; } #[inline] - fn seek_forward(&self, from: usize, needles: [u8; N]) -> Result, InputError> { - Ok(in_slice::seek_forward(&self.mmap, from, needles)) + fn seek_forward(&self, from: usize, needles: [u8; N]) -> Result, Infallible> { + return Ok(if from < self.last_block_start { + self.mmap + .seek_forward(from, needles) + .or_else(|| handle_last(&self.last_block, self.last_block_start, needles)) + } else { + self.as_padded_input().seek_forward(from, needles) + }); + + #[cold] + #[inline(never)] + fn handle_last( + last_block: &PaddedBlock, + offset: usize, + needles: [u8; N], + ) -> Option<(usize, u8)> { + last_block + .bytes() + .seek_forward(0, needles) + .map(|(x, y)| (x + offset, y)) + } } #[inline] - fn seek_non_whitespace_forward(&self, from: usize) -> Result, InputError> { - Ok(in_slice::seek_non_whitespace_forward(&self.mmap, from)) + fn seek_non_whitespace_forward(&self, from: usize) -> Result, Infallible> { + return Ok(if from < self.last_block_start { + self.mmap + .seek_non_whitespace_forward(from) + .or_else(|| handle_last(&self.last_block, self.last_block_start)) + } else { + self.as_padded_input().seek_non_whitespace_forward(from) + }); + + #[cold] + #[inline(never)] + fn handle_last(last_block: &PaddedBlock, offset: usize) -> Option<(usize, u8)> { + last_block + .bytes() + .seek_non_whitespace_forward(0) + .map(|(x, y)| (x + offset, y)) + } } #[inline] fn seek_non_whitespace_backward(&self, from: usize) -> Option<(usize, u8)> { - in_slice::seek_non_whitespace_backward(&self.mmap, from) + return if from < self.last_block_start { + self.mmap.seek_non_whitespace_backward(from) + } else { + self.as_padded_input().seek_non_whitespace_backward(from) + }; } #[inline] - fn is_member_match(&self, from: usize, to: usize, label: &JsonString) -> bool { - in_slice::is_member_match(&self.mmap, from, to, label) + fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> Result { + debug_assert!(from < to); + // The hot path is when we're checking fully within the middle section. + // This has to be as fast as possible, so the "cold" path referring to the TwoSidesPaddedInput + // impl is explicitly marked with #[cold]. + if to < self.last_block_start { + // This is the hot path -- do the bounds check and memcmp. + let bytes = &self.mmap; + let slice = &bytes[from..to]; + Ok(member.bytes_with_quotes() == slice && (from == 0 || bytes[from - 1] != b'\\')) + } else { + // This is a very expensive, cold path. + Ok(self.as_padded_input().is_member_match(from, to, member)) + } } } diff --git a/crates/rsonpath-lib/src/input/owned.rs b/crates/rsonpath-lib/src/input/owned.rs index 5b3e57e2..347ef13c 100644 --- a/crates/rsonpath-lib/src/input/owned.rs +++ b/crates/rsonpath-lib/src/input/owned.rs @@ -1,265 +1,172 @@ -//! Takes ownership of the input data. +//! Takes ownership of bytes of the input document. //! //! Choose this implementation if: //! -//! 1. You already have the data loaded in-memory, but it is not properly -//! aligned, and its size is relatively small, so reallocating it is acceptable – use the -//! [`new`](`OwnedBytes::new`) function for this. +//! 1. You already have the data loaded in-memory. +//! 2. You don't want to deal with ownership and would rather have the input +//! take ownership of the bytes. //! //! ## Performance characteristics //! -//! Runtime performance is the same as for [`BorrowedBytes`]. The overhead comes from -//! the input construction. -//! -//! For data of small length (around a megabyte) full copy is going to be faster still -//! than using a buffered input stream. - -use super::{borrowed::BorrowedBytesBlockIterator, error::InputError, *}; -use crate::{query::JsonString, JSON_SPACE_BYTE}; -use std::{alloc, ptr, slice}; - -/// Input into a query engine. -pub struct OwnedBytes { - bytes_ptr: ptr::NonNull, - len: usize, - capacity: usize, - last_block: LastBlock, +//! This is as fast as [`BorrowedBytes`](`super::BorrowedBytes`), unless +//! the [`Borrow`] implementation of the underlying byte structure is weird +//! and costly. +// === Design note === +// This struct appears to be basically the same as BorrowedBytes, just with different +// ownership mechanics. It appears that it should be possible to have a single struct +// that achieves the API of both, taking either ownership or a borrow, but this leads to +// lifetime issues around the current padding impl. + +use super::{ + align_to, + borrowed::BorrowedBytesBlockIterator, + error::Infallible, + padding::{PaddedBlock, TwoSidesPaddedInput}, + Input, SliceSeekable, MAX_BLOCK_SIZE, +}; +use crate::{query::JsonString, result::InputRecorder}; +use std::borrow::Borrow; + +/// Input wrapping a buffer borrowable as a slice of bytes. +pub struct OwnedBytes { + bytes: B, + middle_len: usize, + first_block: PaddedBlock, + last_block: PaddedBlock, } -impl OwnedBytes { - /// Finalize the initialization of bytes by computing the last_block - /// and producing the final instance. +impl OwnedBytes +where + B: Borrow<[u8]>, +{ + /// Create a new instance of [`OwnedBytes`] taking over the given buffer. /// - /// # Safety: - /// - `ptr` must represent an initialized block of bytes of length `cap` - /// - `len` <= cap - unsafe fn finalize_new(ptr: ptr::NonNull, len: usize, cap: usize) -> Self { - let slice = slice::from_raw_parts(ptr.as_ptr(), len); - let last_block = in_slice::pad_last_block(slice); + /// The input will be automatically padded internally, incurring at most + /// two times [`MAX_BLOCK_SIZE`] of memory overhead. + #[inline(always)] + pub fn new(bytes: B) -> Self { + let (first, middle, last) = align_to::(bytes.borrow()); + let first_block = PaddedBlock::pad_first_block(first); + let last_block = PaddedBlock::pad_last_block(last); Self { - bytes_ptr: ptr, - len, - capacity: cap, + middle_len: middle.len(), + bytes, + first_block, last_block, } } - - /// Get a reference to the bytes as a slice. - #[must_use] - #[inline(always)] - pub fn as_slice(&self) -> &[u8] { - // SAFETY: Pointer is not null and its validity is an internal invariant. - unsafe { slice::from_raw_parts(self.bytes_ptr.as_ptr(), self.len) } - } - - /// Convert to [`BorrowedBytes`]. - #[must_use] - #[inline(always)] - pub fn as_borrowed(&self) -> BorrowedBytes { - // SAFETY: we satisfy both invariants of length divisible by MAX_BLOCK_SIZE - // and alignment to MAX_BLOCK_SIZE. - unsafe { BorrowedBytes::new(self.as_slice()) } - } - - /// Create an instance of [`OwnedBytes`] from raw pointers. - /// - /// # Safety - /// This is highly unsafe, and requires similar invariants as stdlib's [`Vec`]. - /// - /// - `ptr` must have been allocated using the global allocator; - /// - `ptr` must have been allocated with size equal to `size`; - /// - first `size` values that `ptr` points to must be correctly initialized - /// instances of [`u8`]. - /// - the `size` must be not more than [`isize::MAX`]; - /// - `ptr` must be aligned to the [`MAX_BLOCK_SIZE`] boundary; - /// - `size` must be divisible by [`MAX_BLOCK_SIZE`]. - /// - /// Ownership of `ptr` is transferred to the resulting instance. You must ensure - /// there are no other mutable references to its contents and that the memory - /// is not deallocated. - #[must_use] - #[inline(always)] - pub unsafe fn from_raw_parts(ptr: ptr::NonNull, size: usize) -> Self { - Self::finalize_new(ptr, size, size) - } - - /// Copy a buffer of bytes and create a proper [`OwnedBytes`] instance. - /// - /// The contents of the buffer will be copied and might be padded to - /// the [`MAX_BLOCK_SIZE`] boundary. - /// - /// # Errors - /// If the length of the buffer plus - /// the padding exceeds the system limit of [`isize::MAX`], an [`InputError::AllocationSizeExceeded`] - /// error will be raised. - #[inline] - pub fn new>(src: &T) -> Result { - let slice = src.as_ref(); - let rem = slice.len() % MAX_BLOCK_SIZE; - let pad = if rem == 0 { 0 } else { MAX_BLOCK_SIZE - rem }; - let size = slice.len() + pad; - - if size == 0 { - // SAFETY: For len and cap 0 the dangling ptr always works. - return Ok(unsafe { Self::finalize_new(ptr::NonNull::dangling(), 0, 0) }); - } - - // Size overflow check happens in get_layout. - let layout = Self::get_layout(size)?; - - // SAFETY: - // Layout is guaranteed to be of non-zero size at this point. - let raw_ptr = unsafe { alloc::alloc(layout) }; - let ptr = ptr::NonNull::new(raw_ptr).unwrap_or_else(|| alloc::handle_alloc_error(layout)); - - // SAFETY: - unsafe { - ptr::copy_nonoverlapping(slice.as_ptr(), ptr.as_ptr(), slice.len()); - ptr::write_bytes(ptr.as_ptr().add(slice.len()), JSON_SPACE_BYTE, pad); - }; - - // SAFETY: At this point we allocated and initialized exactly `size` bytes. - Ok(unsafe { Self::finalize_new(ptr, size, size) }) - } - - /// Create a new instance of [`OwnedBytes`] from a buffer satisfying - /// all invariants. - /// - /// # Safety - /// The invariants are assumed, not checked. You must ensure that the - /// buffer passed to this function: - /// - has length not exceeding the system cap of [isize::MAX]; - /// - is aligned to the [`MAX_BLOCK_SIZE`] boundary; - /// - has length divisible by [`MAX_BLOCK_SIZE`]. - #[inline] - #[must_use] - pub unsafe fn new_unchecked>(src: &T) -> Self { - let slice = src.as_ref(); - let size = slice.len(); - - if size == 0 { - return Self::finalize_new(ptr::NonNull::dangling(), 0, 0); - } - - let layout = Self::get_layout(size).unwrap_unchecked(); - let raw_ptr = alloc::alloc(layout); - let ptr = ptr::NonNull::new(raw_ptr).unwrap_or_else(|| alloc::handle_alloc_error(layout)); - ptr::copy_nonoverlapping(slice.as_ptr(), ptr.as_ptr(), slice.len()); - - Self::finalize_new(ptr, size, size) - } - - #[inline(always)] - fn get_layout(size: usize) -> Result { - alloc::Layout::from_size_align(size, MAX_BLOCK_SIZE).map_err(|_err| InputError::AllocationSizeExceeded) - } -} - -impl TryFrom for OwnedBytes { - type Error = InputError; - - #[inline] - fn try_from(value: String) -> Result { - Self::try_from(value.into_bytes()) - } } -impl TryFrom> for OwnedBytes { - type Error = InputError; - - #[inline] - fn try_from(value: Vec) -> Result { - Self::new(&value) - } -} - -impl TryFrom<&[u8]> for OwnedBytes { - type Error = InputError; - +impl From for OwnedBytes +where + B: Borrow<[u8]>, +{ #[inline(always)] - fn try_from(value: &[u8]) -> Result { - Self::new(&value) + fn from(value: B) -> Self { + Self::new(value) } } -impl<'a> From<&BorrowedBytes<'a>> for OwnedBytes { +impl From for OwnedBytes> { #[inline(always)] - fn from(value: &BorrowedBytes) -> Self { - // SAFETY: BorrowedBytes satisfies all preconditions by its invariants. - unsafe { Self::new_unchecked(value) } + fn from(value: String) -> Self { + Self::new(value.into_bytes()) } } -impl TryFrom<&str> for OwnedBytes { - type Error = InputError; +impl Input for OwnedBytes +where + B: Borrow<[u8]>, +{ + type BlockIterator<'i, 'r, R, const N: usize> = BorrowedBytesBlockIterator<'r, TwoSidesPaddedInput<'i>, R, N> + where + Self: 'i, + R: InputRecorder> + 'r; - #[inline(always)] - fn try_from(value: &str) -> Result { - Self::try_from(value.as_bytes()) - } -} + type Error = Infallible; -impl Drop for OwnedBytes { - #[inline] - fn drop(&mut self) { - if self.len == 0 { - return; - } - - // This should never happen and if it did it would cause a memory leak. - #[allow(clippy::expect_used)] - let layout = Self::get_layout(self.capacity).expect("layout for existing OwnedBytes must never change"); + type Block<'i, const N: usize> = &'i [u8] + where + Self: 'i; - // SAFETY: - // `ptr` is allocated in `new` and layout is constructed using the same function - // and size. - // This relies on self.capacity not being mutated ever. - unsafe { alloc::dealloc(self.bytes_ptr.as_ptr(), layout) } + #[inline(always)] + fn leading_padding_len(&self) -> usize { + self.first_block.padding_len() } -} - -impl Input for OwnedBytes { - type BlockIterator<'a, 'r, const N: usize, R> = BorrowedBytesBlockIterator<'a, 'r, N, R> - where R: InputRecorder<&'a [u8]> + 'r; - - type Block<'a, const N: usize> = &'a [u8]; #[inline(always)] - fn len_hint(&self) -> Option { - Some((self.len / MAX_BLOCK_SIZE + 1) * MAX_BLOCK_SIZE) + fn trailing_padding_len(&self) -> usize { + self.last_block.padding_len() } - #[inline(always)] - fn iter_blocks<'a, 'r, R, const N: usize>(&'a self, recorder: &'r R) -> Self::BlockIterator<'a, 'r, N, R> + #[inline] + fn iter_blocks<'i, 'r, R, const N: usize>(&'i self, recorder: &'r R) -> Self::BlockIterator<'i, 'r, R, N> where - R: InputRecorder<&'a [u8]>, + R: InputRecorder>, { - BorrowedBytesBlockIterator::new(self.as_slice(), &self.last_block, recorder) + let (_, middle, _) = align_to::(self.bytes.borrow()); + assert_eq!(middle.len(), self.middle_len); + + let padded = TwoSidesPaddedInput::new(&self.first_block, middle, &self.last_block); + + BorrowedBytesBlockIterator::new(padded, recorder) } #[inline] fn seek_backward(&self, from: usize, needle: u8) -> Option { - in_slice::seek_backward(self.as_slice(), from, needle) + let offset = self.leading_padding_len(); + let Some(from) = from.checked_sub(offset) else { + return None; + }; + + self.bytes.borrow().seek_backward(from, needle).map(|x| x + offset) } #[inline] - fn seek_forward(&self, from: usize, needles: [u8; N]) -> Result, InputError> { - Ok(in_slice::seek_forward(self.as_slice(), from, needles)) + fn seek_forward(&self, from: usize, needles: [u8; N]) -> Result, Self::Error> { + let offset = self.leading_padding_len(); + let from = from.saturating_sub(offset); + + Ok(self + .bytes + .borrow() + .seek_forward(from, needles) + .map(|(x, y)| (x + self.leading_padding_len(), y))) } #[inline] - fn seek_non_whitespace_forward(&self, from: usize) -> Result, InputError> { - Ok(in_slice::seek_non_whitespace_forward(self.as_slice(), from)) + fn seek_non_whitespace_forward(&self, from: usize) -> Result, Self::Error> { + let offset = self.leading_padding_len(); + let from = from.saturating_sub(offset); + + Ok(self + .bytes + .borrow() + .seek_non_whitespace_forward(from) + .map(|(x, y)| (x + self.leading_padding_len(), y))) } #[inline] fn seek_non_whitespace_backward(&self, from: usize) -> Option<(usize, u8)> { - in_slice::seek_non_whitespace_backward(self.as_slice(), from) + let offset = self.leading_padding_len(); + let Some(from) = from.checked_sub(offset) else { + return None; + }; + + self.bytes + .borrow() + .seek_non_whitespace_backward(from) + .map(|(x, y)| (x + self.leading_padding_len(), y)) } #[inline] - fn is_member_match(&self, from: usize, to: usize, label: &JsonString) -> bool { - in_slice::is_member_match(self.as_slice(), from, to, label) + fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> Result { + let offset = self.leading_padding_len(); + let Some(from) = from.checked_sub(offset) else { + return Ok(false); + }; + + Ok(self.bytes.borrow().is_member_match(from, to - offset, member)) } } diff --git a/crates/rsonpath-lib/src/input/padding.rs b/crates/rsonpath-lib/src/input/padding.rs new file mode 100644 index 00000000..0726fdc6 --- /dev/null +++ b/crates/rsonpath-lib/src/input/padding.rs @@ -0,0 +1,940 @@ +use super::{SliceSeekable, MAX_BLOCK_SIZE}; +use crate::{query::JsonString, JSON_SPACE_BYTE}; + +pub(super) struct PaddedBlock { + bytes: [u8; MAX_BLOCK_SIZE], + padding_len: usize, +} + +pub struct EndPaddedInput<'a> { + middle: &'a [u8], + last_block: &'a PaddedBlock, +} + +pub struct TwoSidesPaddedInput<'a> { + first_block: &'a PaddedBlock, + middle: &'a [u8], + last_block: &'a PaddedBlock, +} + +impl PaddedBlock { + #[allow(clippy::unused_self)] // This is nicer than using the constant everywhere. + pub(super) const fn len(&self) -> usize { + MAX_BLOCK_SIZE + } + + pub(super) fn padding_len(&self) -> usize { + self.padding_len + } + + pub(super) fn bytes(&self) -> &[u8] { + &self.bytes + } + + pub(super) fn pad_first_block(bytes: &[u8]) -> Self { + assert!(bytes.len() <= MAX_BLOCK_SIZE); + let mut block_buf = [JSON_SPACE_BYTE; MAX_BLOCK_SIZE]; + let block_start = MAX_BLOCK_SIZE - bytes.len(); + + block_buf[block_start..].copy_from_slice(bytes); + + Self { + bytes: block_buf, + padding_len: block_start, + } + } + + pub(super) fn pad_last_block(bytes: &[u8]) -> Self { + assert!(bytes.len() <= MAX_BLOCK_SIZE); + let mut last_block_buf = [JSON_SPACE_BYTE; MAX_BLOCK_SIZE]; + let block_end = bytes.len(); + + last_block_buf[..block_end].copy_from_slice(bytes); + + Self { + bytes: last_block_buf, + padding_len: MAX_BLOCK_SIZE - block_end, + } + } +} + +impl<'a> SliceSeekable for EndPaddedInput<'a> { + #[cold] + #[inline(never)] + fn seek_backward(&self, from: usize, needle: u8) -> Option { + if from < self.middle.len() { + self.seek_backward_from_middle(from, needle) + } else { + self.seek_backward_from_last(from, needle) + } + } + + #[cold] + #[inline(never)] + fn seek_forward(&self, from: usize, needles: [u8; N]) -> Option<(usize, u8)> { + if from < self.middle.len() { + self.seek_forward_from_middle(from, needles) + } else { + self.seek_forward_from_last(from, needles) + } + } + + #[cold] + #[inline(never)] + fn seek_non_whitespace_forward(&self, from: usize) -> Option<(usize, u8)> { + if from < self.middle.len() { + self.seek_non_whitespace_forward_from_middle(from) + } else { + self.seek_non_whitespace_forward_from_last(from) + } + } + + #[cold] + #[inline(never)] + fn seek_non_whitespace_backward(&self, from: usize) -> Option<(usize, u8)> { + if from < self.middle.len() { + self.seek_non_whitespace_backward_from_middle(from) + } else { + self.seek_non_whitespace_backward_from_last(from) + } + } + + #[cold] + #[inline(never)] + fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> bool { + debug_assert!(from < to); + let other = member.bytes_with_quotes(); + self.cold_member_match(other, from, to) + } +} + +impl<'a> SliceSeekable for TwoSidesPaddedInput<'a> { + #[cold] + #[inline(never)] + fn seek_backward(&self, from: usize, needle: u8) -> Option { + if from < MAX_BLOCK_SIZE { + self.seek_backward_from_first(from, needle) + } else if from < self.middle.len() + MAX_BLOCK_SIZE { + self.seek_backward_from_middle(from, needle) + } else { + self.seek_backward_from_last(from, needle) + } + } + + #[cold] + #[inline(never)] + fn seek_forward(&self, from: usize, needles: [u8; N]) -> Option<(usize, u8)> { + if from < MAX_BLOCK_SIZE { + self.seek_forward_from_first(from, needles) + } else if from < self.middle.len() + MAX_BLOCK_SIZE { + self.seek_forward_from_middle(from, needles) + } else { + self.seek_forward_from_last(from, needles) + } + } + + #[cold] + #[inline(never)] + fn seek_non_whitespace_forward(&self, from: usize) -> Option<(usize, u8)> { + if from < MAX_BLOCK_SIZE { + self.seek_non_whitespace_forward_from_first(from) + } else if from < self.middle.len() + MAX_BLOCK_SIZE { + self.seek_non_whitespace_forward_from_middle(from) + } else { + self.seek_non_whitespace_forward_from_last(from) + } + } + + #[cold] + #[inline(never)] + fn seek_non_whitespace_backward(&self, from: usize) -> Option<(usize, u8)> { + if from < MAX_BLOCK_SIZE { + self.seek_non_whitespace_backward_from_first(from) + } else if from < self.middle.len() + MAX_BLOCK_SIZE { + self.seek_non_whitespace_backward_from_middle(from) + } else { + self.seek_non_whitespace_backward_from_last(from) + } + } + + #[cold] + #[inline(never)] + fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> bool { + debug_assert!(from < to); + let other = member.bytes_with_quotes(); + self.cold_member_match(other, from, to) + } +} + +impl<'a> EndPaddedInput<'a> { + pub(super) fn new(middle: &'a [u8], last: &'a PaddedBlock) -> Self { + Self { + middle, + last_block: last, + } + } + + #[inline(always)] + pub(super) fn middle(&self) -> &'a [u8] { + self.middle + } + + fn seek_backward_from_middle(&self, from: usize, needle: u8) -> Option { + debug_assert!(from < self.middle.len()); + let bytes = self.middle; + + seek_backward_impl(bytes, from, needle) + } + + fn seek_backward_from_last(&self, from: usize, needle: u8) -> Option { + debug_assert!(from >= self.middle.len()); + let bytes = &self.last_block.bytes; + + seek_backward_impl(bytes, from - self.middle.len(), needle) + .map(|x| x + self.middle.len()) + .or_else(|| self.seek_backward_from_middle(self.middle.len() - 1, needle)) + } + + fn seek_forward_from_middle(&self, from: usize, needles: [u8; N]) -> Option<(usize, u8)> { + assert!(N > 0); + debug_assert!(from < self.middle.len()); + let bytes = self.middle; + + seek_forward_impl(bytes, from, needles).or_else(|| self.seek_forward_from_last(bytes.len(), needles)) + } + + fn seek_forward_from_last(&self, from: usize, needles: [u8; N]) -> Option<(usize, u8)> { + assert!(N > 0); + debug_assert!(from >= self.middle.len()); + let bytes = &self.last_block.bytes; + + seek_forward_impl(bytes, from - self.middle.len(), needles).map(|(x, y)| (x + self.middle.len(), y)) + } + + fn seek_non_whitespace_forward_from_middle(&self, from: usize) -> Option<(usize, u8)> { + debug_assert!(from < self.middle.len()); + let bytes = self.middle; + + seek_non_whitespace_forward_impl(bytes, from) + .or_else(|| self.seek_non_whitespace_forward_from_last(bytes.len())) + } + + fn seek_non_whitespace_forward_from_last(&self, from: usize) -> Option<(usize, u8)> { + debug_assert!(from >= self.middle.len()); + let bytes = &self.last_block.bytes; + + seek_non_whitespace_forward_impl(bytes, from - self.middle.len()).map(|(x, y)| (x + self.middle.len(), y)) + } + + fn seek_non_whitespace_backward_from_middle(&self, from: usize) -> Option<(usize, u8)> { + debug_assert!(from < self.middle.len()); + let bytes = self.middle; + + seek_non_whitespace_backward_impl(bytes, from) + } + + fn seek_non_whitespace_backward_from_last(&self, from: usize) -> Option<(usize, u8)> { + debug_assert!(from >= self.middle.len()); + let bytes = &self.last_block.bytes; + + seek_non_whitespace_backward_impl(bytes, from - self.middle.len()) + .map(|(x, y)| (x + self.middle.len(), y)) + .or_else(|| self.seek_non_whitespace_backward_from_middle(self.middle.len() - 1)) + } + + pub(super) fn try_slice(&self, start: usize, len: usize) -> Option<&'a [u8]> { + debug_assert!(len < MAX_BLOCK_SIZE); + + if start < self.middle.len() { + self.slice_middle(start, len) + } else { + self.slice_last(start, len) + } + } + + fn slice_middle(&self, start: usize, len: usize) -> Option<&'a [u8]> { + Some(&self.middle[start..start + len]) + } + + fn slice_last(&self, start: usize, len: usize) -> Option<&'a [u8]> { + let start = start - self.middle.len(); + (start < MAX_BLOCK_SIZE).then(|| &self.last_block.bytes[start..start + len]) + } + + /// Slice the entire input from `from` to `to` and return the part + /// from the middle, and the part from the last block. Either or both + /// may be empty when appropriate. + fn slice_parts(&self, from: usize, to: usize) -> (&[u8], &[u8]) { + use std::cmp::min; + + let middle_from = min(from, self.middle.len()); + let middle_to = min(to, self.middle.len()); + + let from = from.saturating_sub(self.middle.len()); + let to = to.saturating_sub(self.middle.len()); + let last_from = min(from, self.last_block.len()); + let last_to = min(to, self.last_block.len()); + + ( + &self.middle[middle_from..middle_to], + &self.last_block.bytes[last_from..last_to], + ) + } + + fn get_at(&self, idx: usize) -> Option { + if idx < self.middle.len() { + Some(self.middle[idx]) + } else if idx < self.middle.len() + MAX_BLOCK_SIZE { + Some(self.last_block.bytes[idx - self.middle.len()]) + } else { + None + } + } + + fn cold_member_match(&self, other: &[u8], from: usize, to: usize) -> bool { + let (middle_self, last_self) = self.slice_parts(from, to); + let middle_other = &other[..middle_self.len()]; + let last_other = &other[middle_self.len()..]; + let preceding_char = from.checked_sub(1).and_then(|x| self.get_at(x)); + + middle_self == middle_other && last_self == last_other && preceding_char.map_or(true, |x| x != b'\\') + } +} + +impl<'a> TwoSidesPaddedInput<'a> { + pub(super) fn new(first: &'a PaddedBlock, middle: &'a [u8], last: &'a PaddedBlock) -> Self { + Self { + first_block: first, + middle, + last_block: last, + } + } + + #[inline(always)] + pub(super) fn middle(&self) -> &'a [u8] { + self.middle + } + + fn seek_backward_from_first(&self, from: usize, needle: u8) -> Option { + debug_assert!(from < MAX_BLOCK_SIZE); + let bytes = &self.first_block.bytes; + + seek_backward_impl(bytes, from, needle) + } + + fn seek_backward_from_middle(&self, from: usize, needle: u8) -> Option { + debug_assert!(from >= MAX_BLOCK_SIZE); + let bytes = self.middle; + + seek_backward_impl(bytes, from - MAX_BLOCK_SIZE, needle) + .map(|x| x + MAX_BLOCK_SIZE) + .or_else(|| self.seek_backward_from_first(MAX_BLOCK_SIZE - 1, needle)) + } + + fn seek_backward_from_last(&self, from: usize, needle: u8) -> Option { + debug_assert!(from >= self.middle.len() + MAX_BLOCK_SIZE); + let bytes = &self.last_block.bytes; + + seek_backward_impl(bytes, from - self.middle.len() - MAX_BLOCK_SIZE, needle) + .map(|x| x + self.middle.len() + MAX_BLOCK_SIZE) + .or_else(|| { + if self.middle.is_empty() { + self.seek_backward_from_first(MAX_BLOCK_SIZE - 1, needle) + } else { + self.seek_backward_from_middle(self.middle.len() + MAX_BLOCK_SIZE - 1, needle) + } + }) + } + + fn seek_forward_from_first(&self, from: usize, needles: [u8; N]) -> Option<(usize, u8)> { + assert!(N > 0); + debug_assert!(from < MAX_BLOCK_SIZE); + let bytes = &self.first_block.bytes; + + seek_forward_impl(bytes, from, needles).or_else(|| { + if self.middle.is_empty() { + self.seek_forward_from_last(bytes.len(), needles) + } else { + self.seek_forward_from_middle(bytes.len(), needles) + } + }) + } + + fn seek_forward_from_middle(&self, from: usize, needles: [u8; N]) -> Option<(usize, u8)> { + assert!(N > 0); + debug_assert!(from >= MAX_BLOCK_SIZE); + let bytes = self.middle; + + seek_forward_impl(bytes, from - MAX_BLOCK_SIZE, needles) + .map(|(x, y)| (x + MAX_BLOCK_SIZE, y)) + .or_else(|| self.seek_forward_from_last(bytes.len() + MAX_BLOCK_SIZE, needles)) + } + + fn seek_forward_from_last(&self, from: usize, needles: [u8; N]) -> Option<(usize, u8)> { + assert!(N > 0); + debug_assert!(from >= self.middle.len() + MAX_BLOCK_SIZE); + let bytes = &self.last_block.bytes; + + seek_forward_impl(bytes, from - self.middle.len() - MAX_BLOCK_SIZE, needles) + .map(|(x, y)| (x + self.middle.len() + MAX_BLOCK_SIZE, y)) + } + + fn seek_non_whitespace_forward_from_first(&self, from: usize) -> Option<(usize, u8)> { + debug_assert!(from < MAX_BLOCK_SIZE); + let bytes = &self.first_block.bytes; + + seek_non_whitespace_forward_impl(bytes, from).or_else(|| { + if self.middle.is_empty() { + self.seek_non_whitespace_forward_from_last(bytes.len()) + } else { + self.seek_non_whitespace_forward_from_middle(bytes.len()) + } + }) + } + + fn seek_non_whitespace_forward_from_middle(&self, from: usize) -> Option<(usize, u8)> { + debug_assert!(from >= MAX_BLOCK_SIZE); + let bytes = self.middle; + + seek_non_whitespace_forward_impl(bytes, from - MAX_BLOCK_SIZE) + .map(|(x, y)| (x + MAX_BLOCK_SIZE, y)) + .or_else(|| self.seek_non_whitespace_forward_from_last(bytes.len() + MAX_BLOCK_SIZE)) + } + + fn seek_non_whitespace_forward_from_last(&self, from: usize) -> Option<(usize, u8)> { + debug_assert!(from >= self.middle.len() + MAX_BLOCK_SIZE); + let bytes = &self.last_block.bytes; + + seek_non_whitespace_forward_impl(bytes, from - self.middle.len() - MAX_BLOCK_SIZE) + .map(|(x, y)| (x + self.middle.len() + MAX_BLOCK_SIZE, y)) + } + + fn seek_non_whitespace_backward_from_first(&self, from: usize) -> Option<(usize, u8)> { + debug_assert!(from < MAX_BLOCK_SIZE); + let bytes = &self.first_block.bytes; + + seek_non_whitespace_backward_impl(bytes, from) + } + + fn seek_non_whitespace_backward_from_middle(&self, from: usize) -> Option<(usize, u8)> { + debug_assert!(from >= MAX_BLOCK_SIZE); + let bytes = self.middle; + + seek_non_whitespace_backward_impl(bytes, from - MAX_BLOCK_SIZE) + .map(|(x, y)| (x + MAX_BLOCK_SIZE, y)) + .or_else(|| self.seek_non_whitespace_backward_from_first(MAX_BLOCK_SIZE - 1)) + } + + fn seek_non_whitespace_backward_from_last(&self, from: usize) -> Option<(usize, u8)> { + debug_assert!(from >= self.middle.len() + MAX_BLOCK_SIZE); + let bytes = &self.last_block.bytes; + + seek_non_whitespace_backward_impl(bytes, from - self.middle.len() - MAX_BLOCK_SIZE) + .map(|(x, y)| (x + self.middle.len() + MAX_BLOCK_SIZE, y)) + .or_else(|| { + if self.middle.is_empty() { + self.seek_non_whitespace_backward_from_first(MAX_BLOCK_SIZE - 1) + } else { + self.seek_non_whitespace_backward_from_middle(self.middle.len() + MAX_BLOCK_SIZE - 1) + } + }) + } + + pub(super) fn try_slice(&self, start: usize, len: usize) -> Option<&'a [u8]> { + debug_assert!(len < MAX_BLOCK_SIZE); + + if start < MAX_BLOCK_SIZE { + Some(self.slice_first(start, len)) + } else if start < self.middle.len() + MAX_BLOCK_SIZE { + Some(self.slice_middle(start, len)) + } else { + self.slice_last(start, len) + } + } + + fn slice_first(&self, start: usize, len: usize) -> &'a [u8] { + &self.first_block.bytes[start..start + len] + } + + fn slice_middle(&self, start: usize, len: usize) -> &'a [u8] { + let start = start - MAX_BLOCK_SIZE; + &self.middle[start..start + len] + } + + fn slice_last(&self, start: usize, len: usize) -> Option<&'a [u8]> { + let start = start - self.middle.len() - MAX_BLOCK_SIZE; + (start < MAX_BLOCK_SIZE).then(|| &self.last_block.bytes[start..start + len]) + } + + /// Slice the entire input from `from` to `to` and return the part + /// from the first block, from the middle, and the part from the last block. + /// Any and all of them may be empty when appropriate. + fn slice_parts(&self, from: usize, to: usize) -> (&[u8], &[u8], &[u8]) { + use std::cmp::min; + + let first_from = min(from, MAX_BLOCK_SIZE); + let first_to = min(to, MAX_BLOCK_SIZE); + + let from = from.saturating_sub(MAX_BLOCK_SIZE); + let to = to.saturating_sub(MAX_BLOCK_SIZE); + let middle_from = min(from, self.middle.len()); + let middle_to = min(to, self.middle.len()); + + let from = from.saturating_sub(self.middle.len()); + let to = to.saturating_sub(self.middle.len()); + let last_from = min(from, self.last_block.len()); + let last_to = min(to, self.last_block.len()); + + ( + &self.first_block.bytes[first_from..first_to], + &self.middle[middle_from..middle_to], + &self.last_block.bytes[last_from..last_to], + ) + } + + fn get_at(&self, idx: usize) -> Option { + if idx < MAX_BLOCK_SIZE { + Some(self.first_block.bytes[idx]) + } else if idx < self.middle.len() + MAX_BLOCK_SIZE { + Some(self.middle[idx - MAX_BLOCK_SIZE]) + } else if idx < self.middle.len() + 2 * MAX_BLOCK_SIZE { + Some(self.last_block.bytes[idx - MAX_BLOCK_SIZE - self.middle.len()]) + } else { + None + } + } + + fn cold_member_match(&self, other: &[u8], from: usize, to: usize) -> bool { + let (first_self, middle_self, last_self) = self.slice_parts(from, to); + let first_other = &other[..first_self.len()]; + let middle_other = &other[first_self.len()..first_self.len() + middle_self.len()]; + let last_other = &other[first_self.len() + middle_self.len()..]; + let preceding_char = from.checked_sub(1).and_then(|x| self.get_at(x)); + + first_self == first_other + && middle_self == middle_other + && last_self == last_other + && preceding_char.map_or(true, |x| x != b'\\') + } +} + +#[inline(always)] +fn seek_backward_impl(bytes: &[u8], from: usize, needle: u8) -> Option { + let mut idx = from; + assert!(idx < bytes.len()); + + loop { + if bytes[idx] == needle { + return Some(idx); + } + if idx == 0 { + return None; + } + idx -= 1; + } +} + +#[inline(always)] +fn seek_forward_impl(bytes: &[u8], from: usize, needles: [u8; N]) -> Option<(usize, u8)> { + let mut idx = from; + if idx >= bytes.len() { + return None; + } + + loop { + let b = bytes[idx]; + if needles.contains(&b) { + return Some((idx, b)); + } + idx += 1; + if idx == bytes.len() { + return None; + } + } +} + +#[inline(always)] +fn seek_non_whitespace_forward_impl(bytes: &[u8], from: usize) -> Option<(usize, u8)> { + let mut idx = from; + if idx >= bytes.len() { + return None; + } + + loop { + let b = bytes[idx]; + if !b.is_ascii_whitespace() { + return Some((idx, b)); + } + idx += 1; + if idx == bytes.len() { + return None; + } + } +} + +#[inline(always)] +fn seek_non_whitespace_backward_impl(bytes: &[u8], from: usize) -> Option<(usize, u8)> { + let mut idx = from; + if idx >= bytes.len() { + return None; + } + + loop { + let b = bytes[idx]; + if !b.is_ascii_whitespace() { + return Some((idx, b)); + } + if idx == 0 { + return None; + } + idx -= 1; + } +} + +#[cfg(test)] +mod test { + use super::{PaddedBlock, JSON_SPACE_BYTE, *}; + use pretty_assertions::assert_eq; + + #[test] + fn on_empty_bytes_is_all_whitespace() { + let result = PaddedBlock::pad_last_block(&[]); + + assert_eq!(result.bytes, [JSON_SPACE_BYTE; MAX_BLOCK_SIZE]); + } + + #[test] + fn on_bytes_smaller_than_full_block_gives_entire_block() { + let bytes = r#"{"test":42}"#.as_bytes(); + + let result = PaddedBlock::pad_last_block(bytes); + + assert_eq!(&result.bytes[0..11], bytes); + assert_eq!(&result.bytes[11..], [JSON_SPACE_BYTE; MAX_BLOCK_SIZE - 11]); + } + + #[test] + fn on_bytes_equal_to_full_block_does_not_change_block() { + let bytes = [42; MAX_BLOCK_SIZE]; + + let result = PaddedBlock::pad_last_block(&bytes); + + assert_eq!(result.bytes, bytes); + } + + mod two_sided_padded_input { + mod seek_forward_1 { + use crate::input::{ + padding::{PaddedBlock, TwoSidesPaddedInput}, + SliceSeekable, + }; + use pretty_assertions::assert_eq; + use std::iter; + + #[test] + fn in_empty_slice_returns_none() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: &[], + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_forward(0, [0]); + + assert_eq!(result, None); + } + + #[test] + fn seeking_from_first_block_from_needle_returns_that() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(r#"{"seek": 42}"#.as_bytes()), + middle: &[], + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_forward(123, [b':']); + + assert_eq!(result, Some((123, b':'))); + } + + #[test] + fn seeking_from_middle_block_from_needle_returns_that() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: r#"{"seek": 42}"#.as_bytes(), + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_forward(128 + 7, [b':']); + + assert_eq!(result, Some((128 + 7, b':'))); + } + + #[test] + fn seeking_from_last_block_from_needle_returns_that() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: &iter::repeat(b' ').take(256).collect::>(), + last_block: &PaddedBlock::pad_last_block(r#"{"seek": 42}"#.as_bytes()), + }; + + let result = input.seek_forward(128 + 256 + 7, [b':']); + + assert_eq!(result, Some((128 + 256 + 7, b':'))); + } + + #[test] + fn seeking_from_first_block_from_not_needle_returns_next_needle() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(r"seek: \t\n42}".as_bytes()), + middle: &[], + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_forward(119, [b'2']); + + assert_eq!(result, Some((126, b'2'))); + } + + #[test] + fn seeking_from_middle_block_from_not_needle_returns_next_needle() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: r"seek: \t\n42}".as_bytes(), + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_forward(128 + 5, [b'2']); + + assert_eq!(result, Some((128 + 11, b'2'))); + } + + #[test] + fn seeking_from_last_block_from_not_needle_returns_next_needle() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: &iter::repeat(b' ').take(256).collect::>(), + last_block: &PaddedBlock::pad_last_block(r"seek: \t\n42}".as_bytes()), + }; + + let result = input.seek_forward(128 + 256 + 5, [b'2']); + + assert_eq!(result, Some((128 + 256 + 11, b'2'))); + } + + #[test] + fn seeking_from_first_block_from_not_needle_when_there_is_no_needle_returns_none() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_forward(5, [b'3']); + + assert_eq!(result, None); + } + } + + mod seek_forward_2 { + use crate::input::{ + padding::{PaddedBlock, TwoSidesPaddedInput}, + SliceSeekable, + }; + use pretty_assertions::assert_eq; + + #[test] + fn in_empty_input_returns_none() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: &[], + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_forward(0, [0, 1]); + + assert_eq!(result, None); + } + + #[test] + fn seeking_from_needle_1_returns_that() { + let bytes = r#"{"seek": 42}"#.as_bytes(); + + let result = bytes.seek_forward(7, [b':', b'4']); + + assert_eq!(result, Some((7, b':'))); + } + + #[test] + fn seeking_from_needle_2_returns_that() { + let bytes = r#"{"seek": 42}"#.as_bytes(); + + let result = bytes.seek_forward(7, [b'4', b':']); + + assert_eq!(result, Some((7, b':'))); + } + + #[test] + fn seeking_from_not_needle_when_next_is_needle_1_returns_that() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_forward(5, [b'4', b'2']); + + assert_eq!(result, Some((8, b'4'))); + } + + #[test] + fn seeking_from_not_needle_when_next_is_needle_2_returns_that() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_forward(5, [b'2', b'4']); + + assert_eq!(result, Some((8, b'4'))); + } + + #[test] + fn seeking_from_not_needle_when_there_is_no_needle_returns_none() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_forward(5, [b'3', b'0']); + + assert_eq!(result, None); + } + } + + mod seek_backward { + use crate::input::{ + padding::{PaddedBlock, TwoSidesPaddedInput}, + SliceSeekable, + }; + use pretty_assertions::assert_eq; + + #[test] + fn in_empty_slice_returns_none() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: &[], + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_non_whitespace_forward(0); + + assert_eq!(result, None); + } + + #[test] + fn seeking_from_needle_returns_that() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: r#"{"seek": 42}"#.as_bytes(), + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_backward(136, b':'); + + assert_eq!(result, Some(135)); + } + + #[test] + fn seeking_from_not_needle_when_previous_is_needle_returns_that() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: "seek: \t\n42}".as_bytes(), + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_backward(137, b'4'); + + assert_eq!(result, Some(136)); + } + + #[test] + fn seeking_from_not_needle_when_there_is_no_needle_returns_none() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: "seek: \t\n42}".as_bytes(), + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_backward(138, b'3'); + + assert_eq!(result, None); + } + } + + mod seek_non_whitespace_forward { + use crate::input::{ + padding::{PaddedBlock, TwoSidesPaddedInput}, + SliceSeekable, + }; + use pretty_assertions::assert_eq; + use std::iter; + + #[test] + fn in_empty_slice_returns_none() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: &[], + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_non_whitespace_forward(0); + + assert_eq!(result, None); + } + + #[test] + fn seeking_from_first_block_from_non_whitespace_returns_that() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(r#"{"seek": 42}"#.as_bytes()), + middle: &[], + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_non_whitespace_forward(123); + + assert_eq!(result, Some((123, b':'))); + } + + #[test] + fn seeking_from_middle_block_from_non_whitespace_returns_that() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: r#"{"seek": 42}"#.as_bytes(), + last_block: &PaddedBlock::pad_last_block(&[]), + }; + + let result = input.seek_non_whitespace_forward(128 + 7); + + assert_eq!(result, Some((128 + 7, b':'))); + } + + #[test] + fn seeking_from_last_block_from_non_whitespace_returns_that() { + let input = TwoSidesPaddedInput { + first_block: &PaddedBlock::pad_first_block(&[]), + middle: &iter::repeat(b' ').take(256).collect::>(), + last_block: &PaddedBlock::pad_last_block(r#"{"seek": 42}"#.as_bytes()), + }; + + let result = input.seek_non_whitespace_forward(128 + 256 + 7); + + assert_eq!(result, Some((128 + 256 + 7, b':'))); + } + + #[test] + fn seeking_from_whitespace_returns_next_non_whitespace() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_non_whitespace_forward(5); + + assert_eq!(result, Some((8, b'4'))); + } + + #[test] + fn seeking_from_whitespace_when_there_is_no_more_non_whitespace_returns_none() { + let bytes = "seek: \t\n ".as_bytes(); + + let result = bytes.seek_non_whitespace_forward(5); + + assert_eq!(result, None); + } + } + } +} diff --git a/crates/rsonpath-lib/src/input/slice.rs b/crates/rsonpath-lib/src/input/slice.rs new file mode 100644 index 00000000..34cb2682 --- /dev/null +++ b/crates/rsonpath-lib/src/input/slice.rs @@ -0,0 +1,352 @@ +use super::SliceSeekable; +use crate::query::JsonString; + +impl> SliceSeekable for T { + fn is_member_match(&self, from: usize, to: usize, member: &JsonString) -> bool { + let bytes = self.as_ref(); + + if to > bytes.len() { + return false; + } + let slice = &bytes[from..to]; + member.bytes_with_quotes() == slice && (from == 0 || bytes[from - 1] != b'\\') + } + + fn seek_backward(&self, from: usize, needle: u8) -> Option { + let bytes = self.as_ref(); + + let mut idx = from; + assert!(idx < bytes.len()); + + loop { + if bytes[idx] == needle { + return Some(idx); + } + if idx == 0 { + return None; + } + idx -= 1; + } + } + + #[inline] + fn seek_forward(&self, from: usize, needles: [u8; N]) -> Option<(usize, u8)> { + let bytes = self.as_ref(); + + assert!(N > 0); + let mut idx = from; + + if idx >= bytes.len() { + return None; + } + + loop { + let b = bytes[idx]; + if needles.contains(&b) { + return Some((idx, b)); + } + idx += 1; + if idx == bytes.len() { + return None; + } + } + } + + #[inline] + fn seek_non_whitespace_forward(&self, from: usize) -> Option<(usize, u8)> { + let bytes = self.as_ref(); + let mut idx = from; + + if idx >= bytes.len() { + return None; + } + + loop { + let b = bytes[idx]; + if !b.is_ascii_whitespace() { + return Some((idx, b)); + } + idx += 1; + if idx == bytes.len() { + return None; + } + } + } + + #[inline] + fn seek_non_whitespace_backward(&self, from: usize) -> Option<(usize, u8)> { + let bytes = self.as_ref(); + let mut idx = from; + + if idx >= bytes.len() { + return None; + } + + loop { + let b = bytes[idx]; + if !b.is_ascii_whitespace() { + return Some((idx, b)); + } + if idx == 0 { + return None; + } + idx -= 1; + } + } +} + +#[cfg(test)] +mod tests { + mod input_block_impl_for_slice { + use crate::input::InputBlock; + use pretty_assertions::assert_eq; + + #[test] + fn halves_splits_in_half() { + let bytes = r#"0123456789abcdef"#.as_bytes(); + + let (half1, half2) = <&[u8] as InputBlock<16>>::halves(&bytes); + + assert_eq!(half1, "01234567".as_bytes()); + assert_eq!(half2, "89abcdef".as_bytes()); + } + } + + mod seek_backward { + use crate::input::SliceSeekable; + use pretty_assertions::assert_eq; + + #[test] + fn seeking_from_before_first_occurrence_returns_none() { + let bytes = r#"{"seek":42}"#.as_bytes(); + + let result = bytes.seek_backward(6, b':'); + + assert_eq!(result, None); + } + + #[test] + fn seeking_from_after_two_occurrences_returns_the_second_one() { + let bytes = r#"{"seek":42,"find":37}"#.as_bytes(); + + let result = bytes.seek_backward(bytes.len() - 1, b':'); + + assert_eq!(result, Some(17)); + } + } + + mod seek_forward_1 { + use crate::input::SliceSeekable; + use pretty_assertions::assert_eq; + + #[test] + fn in_empty_slice_returns_none() { + let bytes = []; + + let result = bytes.seek_forward(0, [0]); + + assert_eq!(result, None); + } + + #[test] + fn seeking_from_needle_returns_that() { + let bytes = r#"{"seek": 42}"#.as_bytes(); + + let result = bytes.seek_forward(7, [b':']); + + assert_eq!(result, Some((7, b':'))); + } + + #[test] + fn seeking_from_not_needle_returns_next_needle() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_forward(5, [b'2']); + + assert_eq!(result, Some((9, b'2'))); + } + + #[test] + fn seeking_from_not_needle_when_there_is_no_needle_returns_none() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_forward(5, [b'3']); + + assert_eq!(result, None); + } + } + + mod seek_forward_2 { + use crate::input::SliceSeekable; + use pretty_assertions::assert_eq; + + #[test] + fn in_empty_slice_returns_none() { + let bytes = []; + + let result = bytes.seek_forward(0, [0, 1]); + + assert_eq!(result, None); + } + + #[test] + fn seeking_from_needle_1_returns_that() { + let bytes = r#"{"seek": 42}"#.as_bytes(); + + let result = bytes.seek_forward(7, [b':', b'4']); + + assert_eq!(result, Some((7, b':'))); + } + + #[test] + fn seeking_from_needle_2_returns_that() { + let bytes = r#"{"seek": 42}"#.as_bytes(); + + let result = bytes.seek_forward(7, [b'4', b':']); + + assert_eq!(result, Some((7, b':'))); + } + + #[test] + fn seeking_from_not_needle_when_next_is_needle_1_returns_that() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_forward(5, [b'4', b'2']); + + assert_eq!(result, Some((8, b'4'))); + } + + #[test] + fn seeking_from_not_needle_when_next_is_needle_2_returns_that() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_forward(5, [b'2', b'4']); + + assert_eq!(result, Some((8, b'4'))); + } + + #[test] + fn seeking_from_not_needle_when_there_is_no_needle_returns_none() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_forward(5, [b'3', b'0']); + + assert_eq!(result, None); + } + } + + mod seek_non_whitespace_forward { + use crate::input::SliceSeekable; + use pretty_assertions::assert_eq; + + #[test] + fn in_empty_slice_returns_none() { + let bytes = []; + + let result = bytes.seek_non_whitespace_forward(0); + + assert_eq!(result, None); + } + + #[test] + fn seeking_from_non_whitespace_returns_that() { + let bytes = r#"{"seek": 42}"#.as_bytes(); + + let result = bytes.seek_non_whitespace_forward(7); + + assert_eq!(result, Some((7, b':'))); + } + + #[test] + fn seeking_from_whitespace_returns_next_non_whitespace() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_non_whitespace_forward(5); + + assert_eq!(result, Some((8, b'4'))); + } + + #[test] + fn seeking_from_whitespace_when_there_is_no_more_non_whitespace_returns_none() { + let bytes = "seek: \t\n ".as_bytes(); + + let result = bytes.seek_non_whitespace_forward(5); + + assert_eq!(result, None); + } + } + + mod seek_non_whitespace_backward { + use crate::input::SliceSeekable; + use pretty_assertions::assert_eq; + + #[test] + fn in_empty_slice_returns_none() { + let bytes = []; + + let result = bytes.seek_non_whitespace_backward(0); + + assert_eq!(result, None); + } + + #[test] + fn seeking_from_non_whitespace_returns_that() { + let bytes = r#"{"seek": 42}"#.as_bytes(); + + let result = bytes.seek_non_whitespace_backward(7); + + assert_eq!(result, Some((7, b':'))); + } + + #[test] + fn seeking_from_whitespace_returns_previous_non_whitespace() { + let bytes = "seek: \t\n42}".as_bytes(); + + let result = bytes.seek_non_whitespace_backward(7); + + assert_eq!(result, Some((4, b':'))); + } + } + + mod is_member_match { + use crate::input::SliceSeekable; + use crate::query::JsonString; + use pretty_assertions::assert_eq; + + #[test] + fn on_exact_match_returns_true() { + let bytes = r#"{"needle":42,"other":37}"#.as_bytes(); + + let result = bytes.is_member_match(1, 9, &JsonString::new("needle")); + + assert_eq!(result, true); + } + + #[test] + fn matching_without_double_quotes_returns_false() { + let bytes = r#"{"needle":42,"other":37}"#.as_bytes(); + + let result = bytes.is_member_match(2, 8, &JsonString::new("needle")); + + assert_eq!(result, false); + } + + #[test] + fn when_match_is_partial_due_to_escaped_double_quote_returns_false() { + let bytes = r#"{"fake\"needle":42,"other":37}"#.as_bytes(); + + let result = bytes.is_member_match(7, 15, &JsonString::new("needle")); + + assert_eq!(result, false); + } + + #[test] + fn when_looking_for_string_with_escaped_double_quote_returns_true() { + let bytes = r#"{"fake\"needle":42,"other":37}"#.as_bytes(); + + let result = bytes.is_member_match(1, 15, &JsonString::new(r#"fake\"needle"#)); + + assert_eq!(result, true); + } + } +} diff --git a/crates/rsonpath-lib/src/lib.rs b/crates/rsonpath-lib/src/lib.rs index 5c34faff..0f79ef4f 100644 --- a/crates/rsonpath-lib/src/lib.rs +++ b/crates/rsonpath-lib/src/lib.rs @@ -6,7 +6,7 @@ //! # Examples //! ```rust //! use rsonpath::engine::{Compiler, Engine, RsonpathEngine}; -//! use rsonpath::input::OwnedBytes; +//! use rsonpath::input::BorrowedBytes; //! use rsonpath::query::JsonPathQuery; //! use rsonpath::result::count::CountRecorder; //! # use std::error::Error; @@ -35,8 +35,8 @@ //! ] //! } //! } -//! "#.to_owned(); -//! let input = OwnedBytes::try_from(contents).unwrap(); +//! "#; +//! let input = BorrowedBytes::new(contents.as_bytes()); //! // Compile the query. The engine can be reused to run the same query on different contents. //! let engine = RsonpathEngine::compile_query(&query)?; //! // Count the number of occurrences of elements satisfying the query. diff --git a/crates/rsonpath-lib/src/query/json_string.rs b/crates/rsonpath-lib/src/query/json_string.rs index 58164315..b6d5f39c 100644 --- a/crates/rsonpath-lib/src/query/json_string.rs +++ b/crates/rsonpath-lib/src/query/json_string.rs @@ -32,6 +32,13 @@ impl std::fmt::Debug for JsonString { } } +impl std::fmt::Display for JsonString { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, r#"{}"#, String::from_utf8_lossy(&self.string_with_quotes)) + } +} + impl JsonString { /// Create a new label from UTF8 input. #[must_use] diff --git a/crates/rsonpath-lib/src/result/approx_span.rs b/crates/rsonpath-lib/src/result/approx_span.rs index 3144fb26..4c165b81 100644 --- a/crates/rsonpath-lib/src/result/approx_span.rs +++ b/crates/rsonpath-lib/src/result/approx_span.rs @@ -12,6 +12,7 @@ pub struct ApproxSpanRecorder<'s, S> { struct InternalRecorder<'s, S> { sink: &'s mut S, + leading_padding_len: usize, match_count: usize, stack: Vec, output_queue: OutputQueue, @@ -26,9 +27,9 @@ struct PartialNode { impl<'s, S> ApproxSpanRecorder<'s, S> { #[inline] - pub(crate) fn new(sink: &'s mut S) -> Self { + pub(crate) fn new(sink: &'s mut S, leading_padding_len: usize) -> Self { Self { - internal: RefCell::new(InternalRecorder::new(sink)), + internal: RefCell::new(InternalRecorder::new(sink, leading_padding_len)), } } } @@ -60,9 +61,10 @@ where } impl<'s, S> InternalRecorder<'s, S> { - fn new(sink: &'s mut S) -> Self { + fn new(sink: &'s mut S, leading_padding_len: usize) -> Self { Self { sink, + leading_padding_len, stack: vec![], match_count: 0, output_queue: OutputQueue::new(), @@ -94,7 +96,7 @@ where idx }; let span = MatchSpan { - start_idx: node.start_idx, + start_idx: node.start_idx - self.leading_padding_len, len: end_idx - node.start_idx, }; self.output_queue.insert(node.id, span); diff --git a/crates/rsonpath-lib/src/result/index.rs b/crates/rsonpath-lib/src/result/index.rs index 028cb45c..35248611 100644 --- a/crates/rsonpath-lib/src/result/index.rs +++ b/crates/rsonpath-lib/src/result/index.rs @@ -6,13 +6,15 @@ use std::cell::RefCell; /// Recorder that saves only the start indices to the [`Sink`]. pub struct IndexRecorder<'s, S> { sink: RefCell<&'s mut S>, + leading_padding_len: usize, } impl<'s, S> IndexRecorder<'s, S> { #[inline] - pub(crate) fn new(sink: &'s mut S) -> Self { + pub(crate) fn new(sink: &'s mut S, leading_padding_len: usize) -> Self { Self { sink: RefCell::new(sink), + leading_padding_len, } } } @@ -35,7 +37,7 @@ where fn record_match(&self, idx: usize, _depth: Depth, _ty: MatchedNodeType) -> Result<(), EngineError> { self.sink .borrow_mut() - .add_match(idx) + .add_match(idx - self.leading_padding_len) .map_err(|err| EngineError::SinkError(Box::new(err))) } diff --git a/crates/rsonpath-lib/src/result/nodes.rs b/crates/rsonpath-lib/src/result/nodes.rs index ca011bb2..a8d02665 100644 --- a/crates/rsonpath-lib/src/result/nodes.rs +++ b/crates/rsonpath-lib/src/result/nodes.rs @@ -25,9 +25,9 @@ where B: Deref, S: Sink, { - pub(crate) fn build_recorder(sink: &'s mut S) -> Self { + pub(crate) fn build_recorder(sink: &'s mut S, leading_padding_len: usize) -> Self { Self { - internal: RefCell::new(InternalRecorder::new(sink)), + internal: RefCell::new(InternalRecorder::new(sink, leading_padding_len)), } } } @@ -148,8 +148,8 @@ where B: Deref, S: Sink, { - fn new(sink: &'s mut S) -> Self { - Self::Simple(SimpleRecorder::new(sink)) + fn new(sink: &'s mut S, leading_padding_len: usize) -> Self { + Self::Simple(SimpleRecorder::new(sink, leading_padding_len)) } #[inline(always)] @@ -196,6 +196,7 @@ struct SimpleRecorder<'s, B, S> { current_block: Option, node: Option, sink: &'s mut S, + leading_padding_len: usize, } struct SimplePartialNode { @@ -210,12 +211,13 @@ where B: Deref, S: Sink, { - fn new(sink: &'s mut S) -> Self { + fn new(sink: &'s mut S, leading_padding_len: usize) -> Self { Self { idx: 0, current_block: None, node: None, sink, + leading_padding_len, } } @@ -253,7 +255,7 @@ where debug!("Committing and outputting node"); self.sink .add_match(Match { - span_start: node.start_idx, + span_start: node.start_idx - self.leading_padding_len, bytes: node.buf, }) .map_err(|err| EngineError::SinkError(Box::new(err)))?; @@ -295,6 +297,7 @@ where }], output_queue: OutputQueue::new(), sink: self.sink, + leading_padding_len: self.leading_padding_len, }, None => StackRecorder { idx: self.idx, @@ -303,6 +306,7 @@ where stack: vec![], output_queue: OutputQueue::new(), sink: self.sink, + leading_padding_len: self.leading_padding_len, }, } } @@ -315,6 +319,7 @@ struct StackRecorder<'s, B, S> { stack: Vec, output_queue: OutputQueue, sink: &'s mut S, + leading_padding_len: usize, } struct PartialNode { @@ -380,7 +385,7 @@ where self.output_queue.insert( node.id, Match { - span_start: node.start_idx, + span_start: node.start_idx - self.leading_padding_len, bytes: node.buf, }, ); @@ -418,6 +423,7 @@ fn append_block(dest: &mut Vec, src: &[u8], src_start: usize, read_start: us #[inline(always)] fn append_final_block(dest: &mut Vec, src: &[u8], src_start: usize, read_start: usize, read_end: usize) { + debug!("src_start: {src_start}, read_start: {read_start}, read_end: {read_end}"); debug_assert!(read_end >= src_start); let in_block_start = if read_start > src_start { read_start - src_start diff --git a/crates/rsonpath-lib/tests/input_implementation_tests.proptest-regressions b/crates/rsonpath-lib/tests/input_implementation_tests.proptest-regressions index 744c7c7d..7e44ee48 100644 --- a/crates/rsonpath-lib/tests/input_implementation_tests.proptest-regressions +++ b/crates/rsonpath-lib/tests/input_implementation_tests.proptest-regressions @@ -4,5 +4,4 @@ # # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. -cc 803def56fed69074f550775bb9c855c59b3fc6d07393a4da8b76500c77db7094 # shrinks to input = [0] -cc ccd1d22d7d0fa2f4428630a9a6416299deefc828e0b564d5f04c42d02c8b7c6a # shrinks to input = [0] +cc eda3f32ad766433cc2f8a6f7968002c161ccd7355a7686e244b4763cb35bcfad # shrinks to (input, from, expected) = ([255], 0, 0) diff --git a/crates/rsonpath-lib/tests/input_implementation_tests.rs b/crates/rsonpath-lib/tests/input_implementation_tests.rs index 0947fb48..6e863fb4 100644 --- a/crates/rsonpath-lib/tests/input_implementation_tests.rs +++ b/crates/rsonpath-lib/tests/input_implementation_tests.rs @@ -1,11 +1,10 @@ use pretty_assertions::assert_eq; use rsonpath::{ input::{error::InputError, *}, + query::JsonString, result::empty::EmptyRecorder, - FallibleIterator, }; -use std::{cmp, fs, iter}; -use std::{fs::File, io::Read}; +use std::{cmp, fs, fs::File, io::Read, iter}; use test_case::test_case; const ROOT_TEST_DIRECTORY: &str = "../rsonpath-test/documents/json/large"; @@ -26,18 +25,19 @@ macro_rules! file_test_cases { file_test_cases!(buffered_input, FileTestInput::Buffered); file_test_cases!(mmap_input, FileTestInput::Mmap); -file_test_cases!(owned_bytes, FileTestInput::Owned); +file_test_cases!(borrowed_bytes, FileTestInput::Borrowed); #[derive(Debug)] enum FileTestInput { Buffered, Mmap, - Owned, + Borrowed, } #[derive(Debug)] enum InMemoryTestInput { Buffered, + Borrowed, Owned, } @@ -52,7 +52,7 @@ impl FileTestInput { match self { FileTestInput::Buffered => Self::test_buffered(path), FileTestInput::Mmap => Self::test_mmap(path), - FileTestInput::Owned => Self::test_owned(path), + FileTestInput::Borrowed => Self::test_borrowed(path), } } @@ -80,77 +80,297 @@ impl FileTestInput { test_equivalence(&buf, input); } - fn test_owned(path: &str) { + fn test_borrowed(path: &str) { let mut buf = vec![]; let mut file = Self::get_file(path); file.read_to_end(&mut buf).unwrap(); - let input = OwnedBytes::new(&buf).unwrap(); + let input = BorrowedBytes::new(&buf); test_equivalence(&buf, input); } } impl InMemoryTestInput { - fn test_on_bytes(&self, bytes: &[u8]) { + fn test_padding(&self, bytes: &[u8]) { + match self { + InMemoryTestInput::Buffered => Self::test_padding_buffered(bytes), + InMemoryTestInput::Borrowed => Self::test_padding_borrowed(bytes), + InMemoryTestInput::Owned => Self::test_padding_owned(bytes), + } + } + + fn test_seek_forward(&self, bytes: &[u8], from: usize, needle: u8, expected: usize) { + match self { + InMemoryTestInput::Buffered => Self::test_seek_forward_buffered(bytes, from, needle, expected), + InMemoryTestInput::Borrowed => Self::test_seek_forward_borrowed(bytes, from, needle, expected), + InMemoryTestInput::Owned => Self::test_seek_forward_owned(bytes, from, needle, expected), + } + } + + fn test_seek_non_whitespace_forward(&self, bytes: &[u8], from: usize, expected: usize, expected_byte: u8) { + match self { + InMemoryTestInput::Buffered => { + Self::test_seek_non_whitespace_forward_buffered(bytes, from, expected, expected_byte) + } + InMemoryTestInput::Borrowed => { + Self::test_seek_non_whitespace_forward_borrowed(bytes, from, expected, expected_byte) + } + InMemoryTestInput::Owned => { + Self::test_seek_non_whitespace_forward_owned(bytes, from, expected, expected_byte) + } + } + } + + fn test_seek_backward(&self, bytes: &[u8], from: usize, needle: u8, expected: usize) { + match self { + InMemoryTestInput::Buffered => Self::test_seek_backward_buffered(bytes, from, needle, expected), + InMemoryTestInput::Borrowed => Self::test_seek_backward_borrowed(bytes, from, needle, expected), + InMemoryTestInput::Owned => Self::test_seek_backward_owned(bytes, from, needle, expected), + } + } + + fn test_seek_non_whitespace_backward(&self, bytes: &[u8], from: usize, expected: usize, expected_byte: u8) { + match self { + InMemoryTestInput::Buffered => { + Self::test_seek_non_whitespace_backward_buffered(bytes, from, expected, expected_byte) + } + InMemoryTestInput::Borrowed => { + Self::test_seek_non_whitespace_backward_borrowed(bytes, from, expected, expected_byte) + } + InMemoryTestInput::Owned => { + Self::test_seek_non_whitespace_backward_owned(bytes, from, expected, expected_byte) + } + } + } + + fn test_positive_is_member_match(&self, bytes: &[u8], from: usize, to: usize, json_string: JsonString) { match self { - InMemoryTestInput::Buffered => Self::test_buffered(bytes), - InMemoryTestInput::Owned => Self::test_owned(bytes), + InMemoryTestInput::Buffered => Self::test_positive_is_member_match_buffered(bytes, from, to, json_string), + InMemoryTestInput::Borrowed => Self::test_positive_is_member_match_borrowed(bytes, from, to, json_string), + InMemoryTestInput::Owned => Self::test_positive_is_member_match_owned(bytes, from, to, json_string), } } - fn test_buffered(bytes: &[u8]) { - let read = ReadBytes(bytes, 0); - let input = BufferedInput::new(read); + fn test_seek_forward_buffered(bytes: &[u8], from: usize, needle: u8, expected: usize) { + let input = create_buffered(bytes); + let result = input.seek_forward(from, [needle]).expect("seek succeeds"); + // Buffered is never padded from the start. + + assert_eq!(result, Some((expected, needle))); + } + + fn test_seek_forward_borrowed(bytes: &[u8], from: usize, needle: u8, expected: usize) { + let input = BorrowedBytes::new(bytes); + let result = input.seek_forward(from, [needle]).expect("seek succeeds"); + // Need to take padding into account. + let expected = expected + input.leading_padding_len(); + + assert_eq!(result, Some((expected, needle))); + } + + fn test_seek_forward_owned(bytes: &[u8], from: usize, needle: u8, expected: usize) { + let input = OwnedBytes::new(bytes); + let result = input.seek_forward(from, [needle]).expect("seek succeeds"); + // Need to take padding into account. + let expected = expected + input.leading_padding_len(); + + assert_eq!(result, Some((expected, needle))); + } + + fn test_seek_non_whitespace_forward_buffered(bytes: &[u8], from: usize, expected: usize, expected_byte: u8) { + let input = create_buffered(bytes); + let result = input.seek_non_whitespace_forward(from).expect("seek succeeds"); + // Buffered is never padded from the start. + + assert_eq!(result, Some((expected, expected_byte))); + } + + fn test_seek_non_whitespace_forward_borrowed(bytes: &[u8], from: usize, expected: usize, expected_byte: u8) { + let input = BorrowedBytes::new(bytes); + let result = input.seek_non_whitespace_forward(from).expect("seek succeeds"); + // Need to take padding into account. + let expected = expected + input.leading_padding_len(); + + assert_eq!(result, Some((expected, expected_byte))); + } + + fn test_seek_non_whitespace_forward_owned(bytes: &[u8], from: usize, expected: usize, expected_byte: u8) { + let input = OwnedBytes::new(bytes); + let result = input.seek_non_whitespace_forward(from).expect("seek succeeds"); + // Need to take padding into account. + let expected = expected + input.leading_padding_len(); + + assert_eq!(result, Some((expected, expected_byte))); + } + + fn test_seek_backward_buffered(bytes: &[u8], from: usize, needle: u8, expected: usize) { + let input = create_buffered(bytes); + // A bit of a hack, make sure we read buffered until at least `from`. + input.seek_forward(from, [bytes[from]]).expect("forwarding succeeds"); + + let result = input.seek_backward(from, needle); + // Buffered is never padded from the start. + + assert_eq!(result, Some(expected)); + } + + fn test_seek_backward_borrowed(bytes: &[u8], from: usize, needle: u8, expected: usize) { + let input = BorrowedBytes::new(bytes); + + // Need to take padding into account. + let from = from + input.leading_padding_len(); + let expected = expected + input.leading_padding_len(); + let result = input.seek_backward(from, needle); + + assert_eq!(result, Some(expected)); + } + + fn test_seek_backward_owned(bytes: &[u8], from: usize, needle: u8, expected: usize) { + let input = OwnedBytes::new(bytes); + + // Need to take padding into account. + let from = from + input.leading_padding_len(); + let expected = expected + input.leading_padding_len(); + let result = input.seek_backward(from, needle); + + assert_eq!(result, Some(expected)); + } + + fn test_seek_non_whitespace_backward_buffered(bytes: &[u8], from: usize, expected: usize, expected_byte: u8) { + let input = create_buffered(bytes); + // A bit of a hack, make sure we read buffered until at least `from`. + input.seek_forward(from, [bytes[from]]).expect("forwarding succeeds"); + + let result = input.seek_non_whitespace_backward(from); + // Buffered is never padded from the start. + + assert_eq!(result, Some((expected, expected_byte))); + } + + fn test_seek_non_whitespace_backward_borrowed(bytes: &[u8], from: usize, expected: usize, expected_byte: u8) { + let input = BorrowedBytes::new(bytes); + + // Need to take padding into account. + let from = from + input.leading_padding_len(); + let expected = expected + input.leading_padding_len(); + let result = input.seek_non_whitespace_backward(from); + + assert_eq!(result, Some((expected, expected_byte))); + } + + fn test_seek_non_whitespace_backward_owned(bytes: &[u8], from: usize, expected: usize, expected_byte: u8) { + let input = OwnedBytes::new(bytes); + + // Need to take padding into account. + let from = from + input.leading_padding_len(); + let expected = expected + input.leading_padding_len(); + let result = input.seek_non_whitespace_backward(from); + + assert_eq!(result, Some((expected, expected_byte))); + } + + fn test_positive_is_member_match_buffered(bytes: &[u8], from: usize, to: usize, json_string: JsonString) { + let input = create_buffered(bytes); + + let result = input.is_member_match(from, to, &json_string).expect("match succeeds"); + // Buffered is never padded from the start. + + assert!(result); + } + + fn test_positive_is_member_match_borrowed(bytes: &[u8], from: usize, to: usize, json_string: JsonString) { + let input = BorrowedBytes::new(bytes); + // Need to take padding into account. + let from = from + input.leading_padding_len(); + let to = to + input.leading_padding_len(); + let result = input.is_member_match(from, to, &json_string).expect("match succeeds"); + + assert!(result); + } + + fn test_positive_is_member_match_owned(bytes: &[u8], from: usize, to: usize, json_string: JsonString) { + let input = OwnedBytes::new(bytes); + + // Need to take padding into account. + let from = from + input.leading_padding_len(); + let to = to + input.leading_padding_len(); + let result = input.is_member_match(from, to, &json_string).expect("match succeeds"); + + assert!(result); + } + + fn test_padding_buffered(bytes: &[u8]) { + let input = create_buffered(bytes); test_equivalence(bytes, input); } - fn test_owned(bytes: &[u8]) { - let input = OwnedBytes::new(&bytes).unwrap(); + fn test_padding_borrowed(bytes: &[u8]) { + let input = BorrowedBytes::new(bytes); + test_equivalence(bytes, input); + } + fn test_padding_owned(bytes: &[u8]) { + let input = OwnedBytes::new(bytes); test_equivalence(bytes, input); } } +fn create_buffered(bytes: &[u8]) -> BufferedInput { + let read = ReadBytes(bytes, 0); + BufferedInput::new(read) +} + fn test_equivalence(original_contents: &[u8], input: I) { let original_length = original_contents.len(); let mut input_contents = read_input_to_end(input).unwrap(); assert_padding_is_correct(&input_contents, original_length); remove_padding(&mut input_contents, original_length); - buffered_assert_eq(&input_contents, original_contents); + buffered_assert_eq(&input_contents.data, original_contents); } -fn read_input_to_end(input: I) -> Result, InputError> { +fn read_input_to_end(input: I) -> Result { let mut result: Vec = vec![]; let mut iter = input.iter_blocks::<_, BLOCK_SIZE>(&EmptyRecorder); - while let Some(block) = iter.next()? { + while let Some(block) = iter.next().map_err(|x| x.into())? { result.extend_from_slice(&block) } - Ok(result) + Ok(ResultInput { + data: result, + leading_padding_len: input.leading_padding_len(), + trailing_padding_len: input.trailing_padding_len(), + }) } -fn assert_padding_is_correct(result: &[u8], original_length: usize) { - assert_eq!(result.len() % BLOCK_SIZE, 0); +fn assert_padding_is_correct(result: &ResultInput, original_length: usize) { + assert_eq!(result.data.len() % BLOCK_SIZE, 0); assert!( - result.len() >= original_length, + result.data.len() >= original_length, "result len ({}) should be at least the original length ({})", - result.len(), + result.data.len(), original_length ); - let padding_length = result.len() - original_length; - let expected_padding: Vec = iter::repeat(b' ').take(padding_length).collect(); + let expected_leading_padding: Vec = iter::repeat(b' ').take(result.leading_padding_len).collect(); + let expected_trailing_padding: Vec = iter::repeat(b' ').take(result.trailing_padding_len).collect(); - assert_eq!(&result[original_length..], expected_padding); + assert_eq!(&result.data[..result.leading_padding_len], expected_leading_padding); + assert_eq!( + &result.data[original_length + result.leading_padding_len..], + expected_trailing_padding + ); } -fn remove_padding(result: &mut Vec, original_length: usize) { - while result.len() > original_length { - result.pop(); - } +fn remove_padding(result: &mut ResultInput, original_length: usize) { + // Remove the leading padding by draining leading_padding_len elems... + result.data.drain(..result.leading_padding_len); + // Now remove the trailing padding by truncating to the original length. + // This works since we removed leading padding first, so the entire difference + // between data.len() and original_length is the trailing padding. + result.data.truncate(original_length); } /// Assert eq on the entire contents results in way too long outputs on failure. @@ -172,6 +392,12 @@ fn buffered_assert_eq(left: &[u8], right: &[u8]) { } } +struct ResultInput { + data: Vec, + leading_padding_len: usize, + trailing_padding_len: usize, +} + struct ReadBytes<'a>(&'a [u8], usize); impl<'a> Read for ReadBytes<'a> { @@ -189,19 +415,209 @@ impl<'a> Read for ReadBytes<'a> { } mod in_memory_proptests { - use proptest::{collection, num, prelude::*}; - use crate::InMemoryTestInput; + use proptest::prelude::*; + use rsonpath::query::JsonString; + const JSON_WHITESPACE_BYTES: [u8; 4] = [b' ', b'\t', b'\n', b'\r']; + + fn decode_whitespace(ws_idx: Vec) -> Vec { + ws_idx.into_iter().map(|x| JSON_WHITESPACE_BYTES[x]).collect() + } proptest! { #[test] - fn buffered_input_represents_the_same_bytes_padded(input in collection::vec(num::u8::ANY, collection::SizeRange::default())) { - InMemoryTestInput::Buffered.test_on_bytes(&input) + fn buffered_input_represents_the_same_bytes_padded(input in prop::collection::vec(prop::num::u8::ANY, 0..1024)) { + InMemoryTestInput::Buffered.test_padding(&input) + } + + #[test] + fn borrowed_bytes_represents_the_same_bytes_padded(input in prop::collection::vec(prop::num::u8::ANY, 0..1024)) { + InMemoryTestInput::Borrowed.test_padding(&input) + } + + #[test] + fn owned_bytes_represents_the_same_bytes_padded(input in prop::collection::vec(prop::num::u8::ANY, 0..1024)) { + InMemoryTestInput::Owned.test_padding(&input) + } + + #[test] + fn buffered_input_seek_forward_is_correct((input, from, expected) in seek_forward_strategy()) { + InMemoryTestInput::Buffered.test_seek_forward(&input, from, 255, expected) + } + + #[test] + fn borrowed_input_seek_forward_is_correct((input, from, expected) in seek_forward_strategy()) { + InMemoryTestInput::Borrowed.test_seek_forward(&input, from, 255, expected) + } + + #[test] + fn owned_input_seek_forward_is_correct((input, from, expected) in seek_forward_strategy()) { + InMemoryTestInput::Owned.test_seek_forward(&input, from, 255, expected) + } + + #[test] + fn buffered_input_seek_non_whitespace_forward_is_correct((input, from, expected) in seek_non_whitespace_forward_strategy()) { + InMemoryTestInput::Buffered.test_seek_non_whitespace_forward(&input, from, expected, 255) + } + + #[test] + fn borrowed_input_seek_non_whitespace_forward_is_correct((input, from, expected) in seek_non_whitespace_forward_strategy()) { + InMemoryTestInput::Borrowed.test_seek_non_whitespace_forward(&input, from, expected, 255) + } + + #[test] + fn owned_input_seek_non_whitespace_forward_is_correct((input, from, expected) in seek_non_whitespace_forward_strategy()) { + InMemoryTestInput::Owned.test_seek_non_whitespace_forward(&input, from, expected, 255) + } + + #[test] + fn buffered_input_seek_backward_is_correct((input, from, expected) in seek_backward_strategy()) { + InMemoryTestInput::Buffered.test_seek_backward(&input, from, 255, expected) + } + + #[test] + fn borrowed_input_seek_backward_is_correct((input, from, expected) in seek_backward_strategy()) { + InMemoryTestInput::Borrowed.test_seek_backward(&input, from, 255, expected) + } + + #[test] + fn owned_input_seek_backward_is_correct((input, from, expected) in seek_backward_strategy()) { + InMemoryTestInput::Owned.test_seek_backward(&input, from, 255, expected) + } + + #[test] + fn buffered_input_seek_non_whitespace_backward_is_correct((input, from, expected) in seek_non_whitespace_backward_strategy()) { + InMemoryTestInput::Buffered.test_seek_non_whitespace_backward(&input, from, expected, 255) + } + + #[test] + fn borrowed_input_seek_non_whitespace_backward_is_correct((input, from, expected) in seek_non_whitespace_backward_strategy()) { + InMemoryTestInput::Borrowed.test_seek_non_whitespace_backward(&input, from, expected, 255) + } + + #[test] + fn owned_input_seek_non_whitespace_backward_is_correct((input, from, expected) in seek_non_whitespace_backward_strategy()) { + InMemoryTestInput::Owned.test_seek_non_whitespace_backward(&input, from, expected, 255) + } + + #[test] + fn buffered_input_is_member_match_should_be_true((input, from, to, member) in positive_is_member_match_strategy()) { + InMemoryTestInput::Buffered.test_positive_is_member_match(&input, from, to, member) + } + + #[test] + fn borrowed_input_is_member_match_should_be_true((input, from, to, member) in positive_is_member_match_strategy()) { + InMemoryTestInput::Borrowed.test_positive_is_member_match(&input, from, to, member) } #[test] - fn owned_bytes_represents_the_same_bytes_padded(input in collection::vec(num::u8::ANY, collection::SizeRange::default())) { - InMemoryTestInput::Owned.test_on_bytes(&input) + fn owned_input_is_member_match_should_be_true((input, from, to, member) in positive_is_member_match_strategy()) { + InMemoryTestInput::Owned.test_positive_is_member_match(&input, from, to, member) + } + } + + prop_compose! { + fn seek_forward_strategy() + (input in prop::collection::vec(0_u8..=254, 1..1024)) + (mut from in 0..input.len(), mut expected in 0..input.len(), mut input in Just(input)) -> (Vec, usize, usize) + { + if expected < from { + std::mem::swap(&mut expected, &mut from); + } + input[expected] = 255; + + (input, from, expected) + } + } + + prop_compose! { + fn seek_backward_strategy() + (input in prop::collection::vec(0_u8..=254, 1..1024)) + (mut from in 0..input.len(), mut expected in 0..input.len(), mut input in Just(input)) -> (Vec, usize, usize) + { + if expected > from { + std::mem::swap(&mut expected, &mut from); + } + input[expected] = 255; + + (input, from, expected) + } + } + + prop_compose! { + fn seek_non_whitespace_forward_strategy() + (ws_idx in prop::collection::vec(0..JSON_WHITESPACE_BYTES.len(), 1..1024)) + (mut from in 0..ws_idx.len(), mut expected in 0..ws_idx.len(), ws_idx in Just(ws_idx)) -> (Vec, usize, usize) + { + if expected < from { + std::mem::swap(&mut expected, &mut from); + } + + let mut input = decode_whitespace(ws_idx); + input[expected] = 255; + + (input, from, expected) + } + } + + prop_compose! { + fn seek_non_whitespace_backward_strategy() + (ws_idx in prop::collection::vec(0..JSON_WHITESPACE_BYTES.len(), 1..1024)) + (mut from in 0..ws_idx.len(), mut expected in 0..ws_idx.len(), ws_idx in Just(ws_idx)) -> (Vec, usize, usize) + { + if expected > from { + std::mem::swap(&mut expected, &mut from); + } + + let mut input = decode_whitespace(ws_idx); + input[expected] = 255; + + (input, from, expected) + } + } + + prop_compose! { + fn positive_is_member_match_strategy() + (input in prop::collection::vec(prop::num::u8::ANY, 2..1024)) + (mut from in 0..input.len(), mut to in 0..input.len(), mut input in Just(input)) -> (Vec, usize, usize, JsonString) + { + if from > to { + std::mem::swap(&mut from, &mut to); + } + + if to - from == 0 { + if from == 0 { + to += 2; + } + else if from == 1 { + from -= 1; + to += 1; + } + else { + from -= 2; + } + } + + if to - from == 1 { + if from == 0 { + to += 1; + } + else { + from -= 1; + } + } + + let str = "x".repeat(to - from - 2); + let json_string = JsonString::new(&str); + let slice = &mut input[from..to]; + + slice.copy_from_slice(json_string.bytes_with_quotes()); + + if from != 0 && input[from - 1] == b'\\' { + input[from - 1] = 255; + } + + (input, from, to, json_string) } } } diff --git a/crates/rsonpath-test-codegen/src/gen.rs b/crates/rsonpath-test-codegen/src/gen.rs index 5f25c713..a974adca 100644 --- a/crates/rsonpath-test-codegen/src/gen.rs +++ b/crates/rsonpath-test-codegen/src/gen.rs @@ -32,7 +32,7 @@ pub(crate) fn generate_test_fns(files: &mut Files) -> Result<(), io::Error> { let mut group_mod = vec![]; for query in &discovered_doc.document.queries { let mut fns = vec![]; - for input_type in [InputTypeToTest::Owned, InputTypeToTest::Buffered, InputTypeToTest::Mmap] { + for input_type in [InputTypeToTest::Borrowed, InputTypeToTest::Buffered, InputTypeToTest::Mmap] { for result_type in get_available_results(&discovered_doc.document.input.source, query)? { let fn_name = format_ident!( "{}", @@ -156,10 +156,10 @@ pub(crate) fn generate_test_fns(files: &mut Files) -> Result<(), io::Error> { let raw_input_path = input_path.as_ref().to_str().expect("supported unicode path"); let code = match input_type { - InputTypeToTest::Owned => { + InputTypeToTest::Borrowed => { quote! { let raw_json = fs::read_to_string(#raw_input_path)?; - let #ident = OwnedBytes::new(&raw_json.as_bytes())?; + let #ident = BorrowedBytes::new(raw_json.as_bytes()); } } InputTypeToTest::Buffered => { @@ -345,7 +345,7 @@ pub(crate) fn generate_imports() -> TokenStream { #[derive(Clone, Copy)] enum InputTypeToTest { - Owned, + Borrowed, Buffered, Mmap, } @@ -370,7 +370,7 @@ impl Display for InputTypeToTest { f, "{}", match self { - Self::Owned => "OwnedBytes", + Self::Borrowed => "BorrowedBytes", Self::Buffered => "BufferedInput", Self::Mmap => "MmapInput", } diff --git a/crates/rsonpath-test/documents/toml/extremely_long_key.toml b/crates/rsonpath-test/documents/toml/extremely_long_key.toml new file mode 100644 index 00000000..53c4d30c --- /dev/null +++ b/crates/rsonpath-test/documents/toml/extremely_long_key.toml @@ -0,0 +1,32 @@ +# Define the JSON input for all query test cases. +[input] +# Short description of the input structure. +description = "Extremely long key over 64k characters" + # Set to true only if your specific test input is fully compressed (no extraneous whitespace). +is_compressed = false + +# Inline JSON document. +[input.source] +json_string = ''' +{ + "This is meant to force the buffered input to have to read ahead to actually compare the entire key. Since we use a very big buffer size (64KiB) we need this long of a label to actually test that code. It is unlikely anyone ever has JSON keys that are *this* long, but hey, correctness is correctness. Litwo! Ojczyzno moja! ty jesteś jak zdrowie. Ile cię trzeba cenić, ten tylko się dowie, Kto cię stracił. Dziś piękność twą w całej ozdobie Widzę i opisuję, bo tęsknię po tobie. Panno Święta, co Jasnej bronisz Częstochowy I w Ostrej świecisz Bramie! Ty, co gród zamkowy Nowogródzki ochraniasz z jego wiernym ludem! Jak mnie dziecko do zdrowia powróciłaś cudem (Gdy od płaczącej matki pod Twoję opiekę Ofiarowany, martwą podniosłem powiekę I zaraz mogłem pieszo do Twych świątyń progu Iść za wrócone życie podziękować Bogu), Tak nas powrócisz cudem na Ojczyzny łono. Tymczasem przenoś moję duszę utęsknioną Do tych pagórków leśnych, do tych łąk zielonych, Szeroko nad błękitnym Niemnem rozciągnionych; Do tych pól malowanych zbożem rozmaitem, Wyzłacanych pszenicą, posrebrzanych żytem; Gdzie bursztynowy świerzop, gryka jak śnieg biała, Gdzie panieńskim rumieńcem dzięcielina pała, A wszystko przepasane, jakby wstęgą, miedzą Zieloną, na niej z rzadka ciche grusze siedzą. Śród takich pól przed laty, nad brzegiem ruczaju, Na pagórku niewielkim, we brzozowym gaju, Stał dwór szlachecki, z drzewa, lecz podmurowany; Świeciły się z daleka pobielane ściany, Tem bielsze, że odbite od ciemnej zieleni Topoli, co go bronią od wiatrów jesieni. Dóm mieszkalny niewielki, lecz zewsząd chędogi, I stodołę miał wielką, i przy niej trzy stogi Użątku, co pod strzechą zmieścić się nie może; Widać, że okolica obfita we zboże, I widać z liczby kopic, co wzdłuż i wszerz smugów Świecą gęsto jak gwiazdy, widać z liczby pługów Orzących wcześnie łany ogromne ugoru, Czarnoziemne, zapewne należne do dworu, Uprawne dobrze na kształt ogrodowych grządek: Że w tym domu dostatek mieszka i porządek. Brama na wciąż otwarta przechodniom ogłasza, Że gościnna i wszystkich w gościnę zaprasza. Właśnie dwókonną bryką wjechał młody panek I obiegłszy dziedziniec zawrócił przed ganek, Wysiadł z powozu; konie porzucone same, Szczypiąc trawę ciągnęły powoli pod bramę. We dworze pusto, bo drzwi od ganku zamknięto Zaszczepkami i kołkiem zaszczepki przetknięto. Podróżny do folwarku nie biegł sług zapytać; Odemknął, wbiegł do domu, pragnął go powitać. Dawno domu nie widział, bo w dalekim mieście Kończył nauki, końca doczekał nareszcie. Wbiega i okiem chciwie ściany starodawne Ogląda czule, jako swe znajome dawne. Też same widzi sprzęty, też same obicia, Z któremi się zabawiać lubił od powicia; Lecz mniej wielkie, mniej piękne, niż się dawniej zdały. I też same portrety na ścianach wisiały. Tu Kościuszko w czamarce krakowskiej, z oczyma Podniesionymi w niebo, miecz oburącz trzyma; Takim był, gdy przysięgał na stopniach ołtarzów, Że tym mieczem wypędzi z Polski trzech mocarzów Albo sam na nim padnie. Dalej w polskiej szacie Siedzi Rejtan żałośny po wolności stracie, W ręku trzymna nóż, ostrzem zwrócony do łona, A przed nim leży Fedon i żywot Katona. Dalej Jasiński, młodzian piękny i posępny, Obok Korsak, towarzysz jego nieodstępny, Stoją na szańcach Pragi, na stosach Moskali, Siekąc wrogów, a Praga już się wkoło pali. Nawet stary stojący zegar kurantowy W drewnianej szafie poznał u wniścia alkowy I z dziecinną radością pociągnął za sznurek, By stary Dąbrowskiego usłyszyć mazurek. Biegał po całym domu i szukał komnaty, Gdzie mieszkał, dzieckiem będąc, przed dziesięciu laty. Wchodzi, cofnął się, toczył zdumione źrenice Po ścianach: w tej komnacie mieszkanie kobiéce? Któż by tu mieszkał? stary stryj nie był żonaty, A ciotka w Petersburgu mieszkała przed laty. To nie był ochmistrzyni pokój! Fortepiano? Na niem noty i książki; wszystko porzucano Niedbale i bezładnie; nieporządek miły! Niestare były rączki, co je tak rzuciły. Tuż i sukienka biała, świeżo z kołka zdjęta Do ubrania, na krzesła poręczu rozpięta. A na oknach donice z pachnącemi ziołki, Geranium, lewkonija, astry i fijołki. Podróżny stanął w jednym z okien - nowe dziwo: W sadzie, na brzegu niegdyś zarosłym pokrzywą, Był maleńki ogródek, ścieżkami porznięty, Pełen bukietów trawy angielskiej i mięty. Drewniany, drobny, w cyfrę powiązany płotek Połyskał się wstążkami jaskrawych stokrotek. Grządki widać, że były świeżo polewane; Tuż stało wody pełne naczynie blaszane, Ale nigdzie nie widać było ogrodniczki; Tylko co wyszła; jeszcze kołyszą się drzwiczki Świeżo trącone; blisko drzwi ślad widać nóżki Na piasku: bez trzewika była i pończoszki; Na piasku drobnym, suchym, białym na kształt śniegu, Ślad wyraźny, lecz lekki; odgadniesz, że w biegu Chybkim był zostawiony nóżkami drobnemi Od kogoś, co zaledwie dotykał się ziemi. Podróżny długo w oknie stał patrząc, dumając, Wonnemi powiewami kwiatów oddychając, Oblicze aż na krzaki fijołkowe skłonił, Oczyma ciekawemi po drożynach gonił I znowu je na drobnych śladach zatrzymywał, Myślał o nich i, czyje były, odgadywał. Przypadkiem oczy podniosł, i tu na parkanie Stała młoda dziewczyna. - Białe jej ubranie Wysmukłą postać tylko aż do piersi kryje, Odsłaniając ramiona i łabędzią szyję. W takiem Litwinka tylko chodzić zwykła z rana, W takiem nigdy nie bywa od mężczyzn widziana: Więc choć świadka nie miała, założyła ręce Na piersiach, przydawając zasłony sukience. Włos w pukle nierozwity, lecz w węzełki małe Pokręcony, schowany w drobne strączki białe, Dziwnie ozdabiał głowę, bo od słońca blasku Świecił się, jak korona na świętych obrazku. Twarzy nie było widać. Zwrócona na pole Szukała kogoś okiem, daleko, na dole; Ujrzała, zaśmiała się i klasnęła w dłonie, Jak biały ptak zleciała z parkanu na błonie I wionęła ogrodem przez płotki, przez kwiaty, I po desce opartej o ścianę komnaty, Nim spostrzegł się, wleciała przez okno, świecąca, Nagła, cicha i lekka jak światłość miesiąca. Nócąc chwyciła suknie, biegła do zwierciadła; Wtem ujrzała młodzieńca i z rąk jej wypadła Suknia, a twarz od strachu i dziwu pobladła. Twarz podróżnego barwą spłonęła rumianą Jak obłok, gdy z jutrzenką napotka się ranną; Skromny młodzieniec oczy zmrużył i przysłonił, Chciał coś mówić, przepraszać, tylko się ukłonił I cofnął się; dziewica krzyknęła boleśnie, Niewyraźnie, jak dziecko przestraszone we śnie; Podróżny zląkł się, spójrzał, lecz już jej nie było, Wyszedł zmieszany i czuł, że serce mu biło Głośno, i sam nie wiedział, czy go miało śmieszyć To dziwaczne spotkanie, czy wstydzić, czy cieszyć. Tymczasem na folwarku nie uszło baczności, Że przed ganek zajechał któryś z nowych gości. Już konie w stajnię wzięto, już im hojnie dano Jako w porządnym domu, i obrok, i siano; Bo Sędzia nigdy nie chciał, według nowej mody, Odsyłać konie gości Żydom do gospody. Słudzy nie wyszli witać, ale nie myśl wcale, Aby w domu Sędziego służono niedbale; Słudzy czekają, nim się pan Wojski ubierze, Który teraz za domem urządzał wieczerzę. On Pana zastępuje i on w niebytności Pana, zwykł sam przyjmować i zabawiać gości (Daleki krewny pański i przyjaciel domu). Widząc gościa, na folwark dążył po kryjomu (Bo nie mógł wyjść spotykać w tkackim pudermanie); Wdział więc, jak mógł najprędzej, niedzielne ubranie, Nagotowane z rana, bo od rana wiedział, Że u wieczerzy będzie z mnóstwem gości siedział. Pan Wojski poznał z dala, ręce rozkrzyżował I z krzykiem podróżnego ściskał i całował; Zaczęła się ta prędka, zmieszana rozmowa, W której lat kilku dzieje chciano zamknąć w słowa Krótkie i poplątane, w ciąg powieści, pytań, Wykrzykników i westchnień, i nowych powitań. Gdy się pan Wojski dosyć napytał, nabadał, Na samym końcu dzieje tego dnia powiadał: \"Dobrze, mój Tadeuszu (bo tak nazywano Młodzieńca, który nosił Kościuszkowskie miano Na pamiątkę, że w czasie wojny się urodził), Dobrze, mój Tadeuszu, żeś się dziś nagodził Do domu, właśnie kiedy mamy panien wiele. Stryjaszek myśli wkrótce sprawić ci wesele; Jest z czego wybrać; u nas towarzystwo liczne Od dni kilku zbiera się na sądy graniczne Dla skończenia dawnego z panem Hrabią sporu; I pan Hrabia ma jutro sam zjechać do dworu; Podkomorzy już zjechał z żoną i z córkami. Młodzież poszła do lasu bawić się strzelbami, A starzy i kobiety żniwo oglądają Pod lasem, i tam pewnie na młodzież czekają. Pójdziemy, jeśli zechcesz, i wkrótce spotkamy Stryjaszka, Podkomorstwo i szanowne damy\". Pan Wojski z Tadeuszem idą pod las drogą I jeszcze się do woli nagadać nie mogą. Słońce ostatnich kresów nieba dochodziło, Mniej silnie, ale szerzej niż we dnie świeciło, Całe zaczerwienione, jak zdrowe oblicze Gospodarza, gdy prace skończywszy rolnicze, Na spoczynek powraca. Już krąg promienisty Spuszcza się na wierzch boru i już pomrok mglisty, Napełniając wierzchołki i gałęzie drzewa, Cały las wiąże w jedno i jakoby zlewa; I bór czernił się na kształt ogromnego gmachu, Słońce nad nim czerwone jak pożar na dachu; Wtem zapadło do głębi; jeszcze przez konary Błysnęło jako świeca przez okienic szpary I zgasło. I wnet sierpy gromadnie dzwoniące We zbożach i grabliska suwane po łące Ucichły i stanęły: tak pan Sędzia każe, U niego ze dniem kończą prace gospodarze. \"Pan świata wie, jak długo pracować potrzeba; Słońce, Jego robotnik, kiedy znidzie z nieba, Czas i ziemianinowi ustępować z pola\". Tak zwykł mawiać pan Sędzia; a Sędziego wola Była ekonomowi poczciwemu świętą; Bo nawet wozy, w które już składać zaczęto Kopę żyta, niepełne jadą do stodoły; Cieszą się z niezwyczajnej ich lekkości woły. Właśnie z lasu wracało towarzystwo całe Wesoło, lecz w porządku; naprzód dzieci małe Z dozorcą, potem Sędzia szedł z Podkomorzyną, Obok pan Podkomorzy otoczon rodziną; Panny tuż za starszemi, a młodzież na boku; Panny szły przed młodzieżą o jakie pół kroku (Tak każe przyzwoitość); nikt tam nie rozprawiał O porządku, nikt mężczyzn i dam nie ustawiał, A każdy mimowolnie porządku pilnował. Bo Sędzia w domu dawne obyczaje chował I nigdy nie dozwalał, by chybiano względu Dla wieku, urodzenia, rozumu, urzędu. \"Tym ładem- mawiał - domy i narody słyną, Z jego upadkiem domy i narody giną\". Więc do porządku wykli domowi i słudzy; I przyjezdny gość, krewny albo człowiek cudzy, Gdy Sędziego nawiedził, skoro pobył mało, Przejmował zwyczaj, którym wszystko oddychało. Krótkie były Sędziego z synowcem witania: Dał mu poważnie rękę do pocałowania I w skroń ucałowawszy, uprzejmie pozdrowił; A choć przez wzgląd na gości niewiele z nim mówił, Widać było z łez, które wylotem kontusza Otarł prędko, jak kochał pana Tadeusza. W ślad gospodarza wszystko ze żniwa i z boru, I z łąk, i z pastwisk razem wracało do dworu. Tu owiec trzoda becząc w ulice się tłoczy I wznosi chmurę pyłu; dalej z wolna kroczy Stado cielic tyrolskich z mosiężnemi dzwonki; Tam konie rżące lecą ze skoszonej łąki; Wszystko bieży ku studni, której ramię z drzewa Raz w raz skrzypi i napój w koryta rozlewa. Sędzia, choć utrudzony, chociaż w gronie gości, Nie chybił gospodarskiej ważnej powinności: Udał się sam ku studni; najlepiej z wieczora Gospodarz widzi, w jakim stanie jest obora; Dozoru tego nigdy sługom nie poruczy, Bo Sędzia wie, że oko pańskie konia tuczy. Wojski z woźnym Protazym ze świecami w sieni Stali i rozprawiali, nieco poróżnieni; Bo w niebytność Wojskiego Woźny po kryjomu Kazał stoły z wieczerzą powynosić z domu I ustawić co prędzej w pośrodku zamczyska, Którego widne były pod lasem zwaliska. Po cóż te przenosiny? Pan Wojski się krzywił I przepraszał Sędziego; Sędzia się zadziwił, Lecz stało się; już późno i trudno zaradzić, Wolał gości przeprosić i w pustki wprowadzić. Po drodze Woźny ciągle Sędziemu tłumaczył, Dlaczego urządzenie pańskie przeinaczył: We dworze żadna izba nie ma obszerności Dostatecznej dla tylu, tak szanownych gości; W zamku sień wielka, jeszcze dobrze zachowana, Sklepienie całe - wprawdzie pękła jedna ściana, Okna bez szyb, lecz latem nic to nie zawadzi; Bliskość piwnic wygodna służącej czeladzi. Tak mówiąc, na Sędziego mrugał; widać z miny, Że miał i taił inne, ważniejsze przyczyny. O dwa tysiące kroków zamek stał za domem, Okazały budową, poważny ogromem, Dziedzictwo starożytnej rodziny Horeszków; Dziedzic zginął był w czasie krajowych zamieszków. Dobra, całe zniszczone sekwestrami rządu, Bezładnością opieki, wyrokami sądu, W cząstce spadły dalekim krewnym po kądzieli, A resztę rozdzielono między wierzycieli. Zamku żaden wziąść nie chciał, bo w szlacheckim stanie Trudno było wyłożyć koszt na utrzymanie; Lecz Hrabia, sąsiad bliski, gdy wyszedł z opieki, Panicz bogaty, krewny Horeszków daleki, Przyjechawszy z wojażu upodobał mury, Tłumacząc, że gotyckiej są architektury; Choć Sędzia z dokumentów przekonywał o tem, Że architekt był majstrem z Wilna, nie zaś Gotem. Dość, że Hrabia chciał zamku, właśnie i Sędziemu Przyszła nagle taż chętka, nie wiadomo czemu. Zaczęli proces w ziemstwie, potem w głównym sądzie, W senacie, znowu w ziemstwie i w guberskim rządzie; Wreszcie po wielu kosztach i ukazach licznych Sprawa wróciła znowu do sądów granicznych. Słusznie Woźny powiadał, że w zamkowej sieni Zmieści się i palestra, i goście proszeni. Sień wielka jak refektarz, z wypukłym sklepieniem Na filarach, podłoga wysłana kamieniem, Ściany bez żadnych ozdób, ale mur chędogi; Sterczały wkoło sarnie i jelenie rogi Z napisami: gdzie, kiedy te łupy zdobyte; Tuż myśliwców herbowne klejnoty wyryte I stoi wypisany każdy po imieniu; Herb Horeszków, Półkozic, jaśniał na sklepieniu. Goście weszli w porządku i stanęli kołem; Podkomorzy najwyższe brał miejsce za stołem; Z wieku mu i z urzędu ten zaszczyt należy. Idąc kłaniał się damom, starcom i młodzieży. Przy nim stał Kwestarz, Sędzia tuż przy Bernardynie. Bernardyn zmówił krótki pacierz po łacinie. Mężczyznom dano wódkę; wtenczas wszyscy siedli I chołodziec litewski milcząc żwawo jedli. Pan Tadeusz, choć młodzik, ale prawem gościa Wysoko siadł przy damach obok Jegomościa; Między nim i stryjaszkiem jedno pozostało Puste miejsce, jak gdyby na kogoś czekało. Stryj nieraz na to miejsce i na drzwi poglądał, Jakby czyjegoś przyjścia był pewny i żądał. I Tadeusz wzrok stryja ku drzwiom odprowadzał, I z nim na miejscu pustym oczy swe osadzał. Dziwna rzecz! Miejsca wkoło są siedzeniem dziewic, Na które mógłby spojrzeć bez wstydu królewic, Wszystkie zacnie zrodzone, każda młoda, ładna; Tadeusz tam pogląda, gdzie nie siedzi żadna. To miejsce jest zagadką, młódź lubi zagadki; Roztargniony, do swojej nadobnej sąsiadki Ledwo słów kilka wyrzekł, do Podkomorzanki; Nie zmienia jej talerzów, nie nalewa szklanki, I panien nie zabawia przez rozmowy grzeczne, Z których by wychowanie poznano stołeczne; To jedno puste miejsce nęci go i mami... Już nie puste, bo on je napełnił myślami. Po tem miejscu biegało domysłów tysiące, Jako po deszczu żabki po samotnej łące; Śród nich jedna króluje postać, jak w pogodę Lilia jeziór skroń białą wznosząca nad wodę. Dano trzecią potrawę. Wtem pan Podkomorzy, Wlawszy kropelkę wina w szklankę panny Róży, A młodszej przysunąwszy z talerzem ogórki, Rzekł: \"Muszę ja wam służyć, moje panny córki, Choć stary i niezgrabny\". Zatem się rzuciło Kilku młodych od stołu i pannom służyło. Sędzia, z boku rzuciwszy wzrok na Tadeusza I poprawiwszy nieco wylotów kontusza, Nalał węgrzyna i rzekł: \"Dziś nowym zwyczajem, My na naukę młodzież do stolicy dajem I nie przeczym, że nasi synowie i wnuki Mają od starych więcej książkowej nauki; Ale co dzień postrzegam, jak młódź cierpi na tem, Że nie ma szkół uczących żyć z ludźmi i światem. Dawniej na dwory pańskie jachał szlachcic młody, Ja sam lat dziesięć byłem dworskim Wojewody, Ojca Podkomorzego, Mościwego Pana (Mówiąc, Podkomorzemu ścisnął za kolana); On mnie radą do usług publicznych sposobił, Z opieki nie wypuścił, aż człowiekiem zrobił. W mym domu wiecznie będzie jego pamięć droga, Co dzień za duszę jego proszę Pana Boga. Jeślim tyle na jego nie korzystał dworze Jak drudzy i wróciwszy w domu ziemię orzę, Gdy inni, więcej godni Wojewody względów, Doszli potem najwyższych krajowych urzędów, Przynajmniej tom skorzystał, że mi w moim domu Nikt nigdy nie zarzuci, bym uchybił komu W uczciwości, w grzeczności; a ja powiem śmiało: Grzeczność nie jest nauką łatwą ani małą. Niełatwą, bo nie na tym kończy się, jak nogą Zręcznie wierzgnąć, z uśmiechem witać lada kogo; Bo taka grzeczność modna zda mi się kupiecka, Ale nie staropolska, ani też szlachecka. Grzeczność wszystkim należy, lecz każdemu inna; Bo nie jest bez grzeczności i miłość dziecinna, I wzgląd męża dla żony przy ludziach, i pana Dla sług swoich, a w każdej jest pewna odmiana. Trzeba się długo uczyć, ażeby nie zbłądzić I każdemu powinną uczciwość wyrządzić. I starzy się uczyli; u panów rozmowa Była to historyja żyjąca krajowa, A między szlachtą dzieje domowe powiatu: Dawano przez to poznać szlachcicowi bratu, Że wszyscy o nim wiedzą, lekce go nie ważą; Więc szlachcic obyczaje swe trzymał pod strażą. Dziś człowieka nie pytaj: co zacz? kto go rodzi? Z kim on żył, co porabiał? każdy, gdzie chce, wchodzi, Byle nie szpieg rządowy i byle nie w nędzy. Jak ów Wespazyjanus nie wąchał pieniędzy I nie chciał wiedzieć, skąd są, z jakich rąk i krajów, Tak nie chcą znać człowieka rodu, obyczajów! Dość, że ważny i że się stempel na nim widzi, Więc szanują przyjaciół jak pieniądze Żydzi\". To mówiąc Sędzia gości obejrzał porządkiem; Bo choć zawsze i płynnie mówił, i z rozsądkiem, Wiedział, że niecierpliwa młodzież teraźniejsza, Że ją nudzi rzecz długa, choć najwymowniejsza. Ale wszyscy słuchali w milczeniu głębokiem; Sędzia Podkomorzego zdał się radzić okiem, Podkomorzy pochwałą rzeczy nie przerywał, Ale częstem skinieniem głowy potakiwał. Sędzia milczał, on jeszcze skinieniem przyzwalał; Więc Sędzia jego puchar i swój kielich nalał I dalej mówił: \"Grzeczność nie jest rzeczą małą: Kiedy się człowiek uczy ważyć, jak przystało, Drugich wiek, urodzenie, cnoty, obyczaje, Wtenczas i swoją ważność zarazem poznaje; Jak na szalach żebyśmy nasz ciężar poznali, Musim kogoś posadzić na przeciwnej szali. Zaś godna jest Waszmościów uwagi osobnej Grzeczność, którą powinna młodź dla płci nadobnej ; Zwłaszcza gdy zacność domu, fortuny szczodroty Objaśniają wrodzone wdzięki i przymioty. Stąd droga do afektów i stąd się kojarzy Wspaniały domów sojusz - tak myślili starzy. A zatem...\" Tu Pan Sędzia nagłym zwrotem głowy Skinął na Tadeusza, rzucił wzrok surowy, Znać było, że przychodził już do wniosków mowy. Wtem brząknął w tabakierkę złotą Podkomorzy I rzekł: \"Mój Sędzio, dawniej było jeszcze gorzéj! Teraz nie wiem, czy moda i nas starych zmienia, Czy młodzież lepsza, ale widzę mniej zgorszenia. Ach, ja pamiętam czasy, kiedy do Ojczyzny Pierwszy raz zawitała moda francuszczyzny! Gdy raptem paniczyki młode z cudzych krajów Wtargnęli do nas hordą gorszą od Nogajów! Prześladując w Ojczyźnie Boga, przodków wiarę, Prawa i obyczaje, nawet suknie stare. Żałośnie było widzieć wyżółkłych młokosów, Gadających przez nosy, a często bez nosów, Opatrzonych w broszurki i w różne gazety, Głoszących nowe wiary, prawa, toalety. Miała nad umysłami wielką moc ta tłuszcza; Bo Pan Bóg, kiedy karę na naród przepuszcza, Odbiera naprzód rozum od obywateli. I tak mędrsi fircykom oprzeć się nie śmieli; I zląkł ich się jak dżumy jakiej cały naród, Bo już sam wewnątrz siebie czuł choroby zaród. Krzyczano na modnisiów, a brano z nich wzory: Zmieniano wiarę, mowę, prawa i ubiory. Była to maszkarada, zapustna swawola, Po której miał przyjść wkrótce wielki post - niewola! Pamiętam, chociaż byłem wtenczas małe dziecię, Kiedy do ojca mego w oszmiańskim powiecie Przyjechał pan Podczaszyc na francuskim wózku, Pierwszy człowiek, co w Litwie chodził po francusku. Biegali wszyscy za nim jakby za rarogiem, Zazdroszczono domowi, przed którego progiem Stanęła Podczaszyca dwukolna dryndulka, Która się po francusku zwała karyjulka. Zamiast lokajów w kielni siedziały dwa pieski, A na kozłach niemczysko chude na kształt deski; Nogi miał długie, cienkie, jak od chmielu tyki, W pończochach, ze srebrnemi klamrami trzewiki, Peruka z harbajtelem zawiązanym w miechu. Starzy na on ekwipaż parskali ze śmiechu, A chłopi żegnali się, mowiąc, że po świecie Jeździ wenecki diabeł w niemieckiej karecie. Sam Podczaszyc jaki był, opisywać długo; Dosyć, że się nam zdawał małpą lub papugą, W wielkiej peruce, którą do złotego runa On lubił porównywać, a my do kołtuna. Jeśli kto i czuł wtenczas, że polskie ubranie Piękniejsze jest niż obcej mody małpowanie, Milczał; boby krzyczała młodzież, że przeszkadza Kulturze, że tamuje progresy, że zdradza! Taka była przesądów owoczesnych władza! \"Podczaszyc zapowiedział, że nas reformować, Cywilizować będzie i konstytuować; Ogłosił nam, że jacyś Francuzi wymowni Zrobili wynalazek, iż ludzie są rowni. Choć o tym dawno w Pańskim pisano zakonie I każdy ksiądz toż samo gada na ambonie. Nauka dawną była, szło o jej pełnienie! Lecz wtenczas panowało takie oślepienie, Że nie wierzono rzeczom najdawniejszym w świecie, Jeśli ich nie czytano w francuskiej gazecie. Podczaszyc, mimo równość, wziął tytuł markiża; Wiadomo, że tytuły przychodzą z Paryża, A natenczas tam w modzie był tytuł markiża. Jakoż, kiedy się moda odmieniła z laty, Tenże sam markiż przybrał tytuł demokraty; Wreszcie z odmienną modą, pod Napoleonem, Demokrata przyjechał z Paryża baronem; Gdyby żył dłużej, może nową alternatą Z barona przechrzciłby się kiedyś demokratą. Bo Paryż częstą mody odmianą się chlubi, A co Francuz wymyśli, to Polak polubi. Chwała Bogu, że teraz jeśli nasza młodzież Wyjeżdża za granicę, to już nie po odzież, Nie szukać prawodawstwa w drukarskich kramarniach Lub wymowy uczyć się w paryskich kawiarniach. Bo teraz Napoleon, człek mądry a prędki, Nie daje czasu szukać mody i gawędki. Teraz grzmi oręż, a nam starym serca rosną, Że znowu o Polakach tak na świecie głośno; Jest sława, a więc będzie i Rzeczpospolita! Zawżdy z wawrzynów drzewo wolności wykwita. Tylko smutno, że nam, ach! tak się lata wleką W nieczynności! a oni tak zawsze daleko! Tak długo czekać! Nawet tak rzadka nowina! Ojcze Robaku (ciszej rzekł do Bernardyna), Słyszałem, żeś zza Niemna odebrał wiadomość; Może też co o naszym wojsku wie Jegomość?\" \"Nic a nic - odpowiedział Robak obojętnie (Widać było, że słuchał rozmowy niechętnie) - Mnie polityka nudzi; jeżeli z Warszawy Mam list, to rzecz zakonna, to są nasze sprawy Bernardyńskie; cóż o tem gadać u wieczerzy? Są tu świeccy, do których nic to nie należy\". Tak mowiąc spojrzał zyzem, gdzie śród biesiadników Siedział gość Moskal; był to pan kapitan Ryków; Stary żołnierz, stał w bliskiej wiosce na kwaterze, Pan Sędzia go przez grzeczność prosił na wieczerzę. Rykow jadł smaczno, mało wdawał się w rozmowę, Lecz na wzmiankę Warszawy rzekł, podniosłszy głowę: \"Pan Podkomorzy! Oj, Wy! Pan zawsze ciekawy O Bonaparta, zawsze Wam tam do Warszawy! He! Ojczyzna! Ja nie szpieg, a po polsku umiem - Ojczyzna! Ja to czuję wszystko, ja rozumiem! Wy Polaki, ja Ruski, teraz się nie bijem, Jest armistycjum, to my razem jemy, pijem. Często na awanpostach nasz z Francuzem gada, Pije wódkę; jak krzykną: ura! - kanonada. Ruskie przysłowie: Z kim się biję, tego lubię; Gładź drużkę jak po duszy, a bij jak po szubie. Ja mówię, będzie wojna u nas. Do majora Płuta adiutant sztabu przyjechał zawczora: Gotować się do marszu! Pójdziem, czy pod Turka, Czy na Francuza; oj, ten Bonapart figurka! Bez Suwarowa to on może nas wytuza. U nas w pułku gadano, jak szli na Francuza, Że Bonapart czarował, no, tak i Suwarów Czarował; tak i były czary przeciw czarów. Raz w bitwie, gdzie podział się? szukać Bonaparta - A on zmienił się w lisa, tak Suwarów w charta; Tak Bonaparte znowu w kota się przerzuca, Dalej drzeć pazurami, a Suwarów w kuca. Obaczcież, co się stało w końcu z Bonapartą...\" Tu Ryków przerwał i jadł; wtem z potrawą czwartą Wszedł służący i raptem boczne drzwi otwarto. Weszła nowa osoba, przystojna i młoda; Jej zjawienie się nagłe, jej wzrost i uroda, Jej ubiór zwrócił oczy; wszyscy ją witali; Prócz Tadeusza, widać, że ją wszyscy znali. Kibić miała wysmukłą, kształtną, pierś powabną, Suknię materyjalną, różową, jedwabną, Gors wycięty, kołnierzyk z korónek, rękawki Krótkie, w ręku kręciła wachlarz dla zabawki (Bo nie było gorąca); wachlarz pozłocisty Powiewając rozlewał deszcz iskier rzęsisty. Głowa do włosów, włosy pozwijane w kręgi, W pukle i przeplatane różowemi wstęgi, Pośród nich brylant, niby zakryty od oczu, Świecił się jako gwiazda w komety warkoczu - Słowem, ubior galowy; szeptali niejedni, Że zbyt wykwintny na wieś i na dzień powszedni. Nóżek, choć suknia krótka, oko nie zobaczy, Bo biegła bardzo szybko, suwała się raczéj, Jako osobki, które na trzykrólskie święta Przesuwają w jasełkach ukryte chłopięta. Biegła i wszystkich lekkim witając ukłonem, Chciała usieść na miejscu sobie zostawionem. Trudno było; bo krzeseł dla gości nie stało: Na czterech ławach cztery ich rzędy siedziało, Trzeba było rzęd ruszyć lub ławę przeskoczyć; Zręcznie między dwie ławy umiała się wtłoczyć, A potem między rzędem siedzących i stołem Jak bilardowa kula toczyła się kołem. W biegu dotknęła blisko naszego młodziana; Uczepiwszy falbaną o czyjeś kolana, Pośliznęła się nieco i w tym roztargnieniu Na pana Tadeusza wsparła się ramieniu. Przeprosiwszy go grzecznie, na miejscu swym siadła Pomiędzy nim i stryjem, ale nic nie jadła, Tylko się wachlowała, to wachlarza trzonek Kręciła, to kołnierzyk z brabanckich koronek Poprawiała, to lekkiem dotknięciem się ręki Muskała włosów pukle i wstąg jasnych pęki. Ta przerwa rozmów trwała już minut ze cztery. Tymczasem w końcu stoła naprzód ciche szmery, A potem się zaczęły wpółgłośne rozmowy; Mężczyźni rozsądzali swe dzisiejsze łowy. Asesora z Rejentem wzmogła się uparta, Coraz głośniejsza kłótnia o kusego charta, Którego posiadaniem pan Rejent się szczycił I utrzymywał, że on zająca pochwycił; Asesor zaś dowodził na złość Rejentowi, Że ta chwała należy chartu Sokołowi. Pytano zdania innych; więc wszyscy dokoła Brali stronę Kusego, albo też Sokoła, Ci jak znawcy, ci znowu jak naoczne świadki. Sędzia na drugim końcu do nowej sąsiadki Rzekł półgłosem: \"Przepraszam, musieliśmy siadać, Niepodobna wieczerzy na później odkładać: Goście głodni, chodzili daleko na pole; Myśliłem, że dziś z nami nie będziesz przy stole.\" To rzekłszy, z Podkomorzym przy pełnym kielichu O politycznych sprawach rozmawiał po cichu. Gdy tak były zajęte stołu strony obie, Tadeusz przyglądał się nieznanej osobie: Przypomniał, że za pierwszem na miejsce wejrzeniem Odgadnął zaraz, czyjem miało być siedzeniem. Rumienił się, serce mu biło nadzwyczajnie; Więc rozwiązane widział swych domysłów tajnie! Więc było przeznaczono, by przy jego boku Usiadła owa piękność widziana w pomroku. Wprawdzie zdała się teraz wzrostem dorodniejsza, Bo ubrana, a ubiór powiększa i zmniejsza. I włos u tamtej widział krótki, jasnozłoty, A u tej krucze, długie zwijały się sploty? Kolor musiał pochodzić od słońca promieni, Któremi przy zachodzie wszystko się czerwieni. Twarzy wówczas nie dostrzegł, nazbyt rychło znikła, Ale myśl twarz nadobną odgadywać zwykła; Myślił, że pewnie miała czarniutkie oczęta, Białą twarz, usta kraśne jak wiśnie bliźnięta; U tej znalazł podobne oczy, usta, lica; W wieku może by była największa różnica: Ogrodniczka dziewczynką zdawała się małą, A pani ta niewiastą już w latach dojrzałą; Lecz młodzież o piękności metrykę nie pyta, Bo młodzieńcowi młodą jest każda kobiéta, Chłopcowi każda piękność zda się rówiennicą, A niewinnemu każda kochanka dziewicą. Tadeusz, chociaż liczył lat blisko dwadzieście I od dzieciństwa mieszkał w Wilnie, wielkim mieście, Miał za dozorcę księdza, który go pilnował I w dawnej surowości prawidłach wychował. Tadeusz zatem przywiozł w strony swe rodzinne Duszę czystą, myśl żywą i serce niewinne, Ale razem niemałą chętkę do swywoli. Z góry już robił projekt, że sobie pozwoli Używać na wsi długo wzbronionej swobody; Wiedział, że był przystojny, czuł się rześki, młody, A w spadku po rodzicach wziął czerstwość i zdrowie. Nazywał się Soplica; wszyscy Soplicowie Są, jak wiadomo, krzepcy, otyli i silni, Do żołnierki jedyni, w naukach mniej pilni. Tadeusz się od przodków swoich nie odrodził: Dobrze na koniu jeździł, pieszo dzielnie chodził, Tępy nie był, lecz mało w naukach postąpił, Choć stryj na wychowanie niczego nie skąpił. On wolał z flinty strzelać albo szablą robić; Wiedział, że go myślano do wojska sposobić, Że ojciec w testamencie wyrzekł taką wolę; Ustawicznie do bębna tęsknił, siedząc w szkole. Ale stryj nagle pierwsze zamiary odmienił, Kazał, aby przyjechał i aby się żenił, I objął gospodarstwo; przyrzekł na początek Dać małą wieś, a potem cały swój majątek. Te wszystkie Tadeusza cnoty i zalety Ściągnęły wzrok sąsiadki, uważnej kobiety. Zmierzyła jego postać kształtną i wysoką, Jego ramiona silne, jego pierś szeroką I w twarz spójrzała, z której wytryskał rumieniec, Ilekroć z jej oczyma spotkał się młodzieniec: Bo z pierwszej lękliwości całkiem już ochłonął I patrzył wzrokiem śmiałym, w którym ogień płonął. Również patrzyła ona i cztery źrenice Gorzały przeciw sobie jak roratne świéce. Pierwsza z nim po francusku zaczęła rozmowę; Wracał z miasta, ze szkoły: więc o książki nowe, O autorów pytała Tadeusza zdania I ze zdań wyciągała na nowo pytania; Cóż gdy potem zaczęła mówić o malarstwie, O muzyce, o tańcach, nawet o rzeźbiarstwie! Dowiodła, że zna równie pędzel, noty, druki; Aż osłupiał Tadeusz na tyle nauki, Lękał się, by nie został pośmiewiska celem, I jąkał się jak żaczek przed nauczycielem. Szczęściem, że nauczyciel ładny i niesrogi; Odgadnęła sąsiadka powód jego trwogi, Wszczęła rzecz o mniej trudnych i mądrych przedmiotach: O wiejskiego pożycia nudach i kłopotach, I jak bawić się trzeba, i jak czas podzielić, By życie uprzyjemnić i wieś rozweselić. Tadeusz odpowiadał śmielej, szła rzecz daléj, W pół godziny już byli z sobą poufali; Zaczęli nawet małe żarciki i sprzeczki. W końcu stawiła przed nim trzy z chleba gałeczki: Trzy osoby na wybor; wziął najbliższą sobie; Podkomorzanki na to zmarszczyły się obie, Sąsiadka zaśmiała się, lecz nie powiedziała, Kogo owa szczęśliwsza gałka oznaczała. Inaczej bawiono się w drugim końcu stoła, Bo tam, wzmogłszy się nagle, stronnicy Sokoła Na partyję Kusego bez litości wsiedli: Spór był wielki, już potraw ostatnich nie jedli. Stojąc i pijąc obie kłóciły się strony, A najstraszniej pan Rejent był zacietrzewiony: Jak raz zaczął, bez przerwy rzecz swoją tokował I gestami ją bardzo dobitnie malował. (Był dawniej adwokatem pan rejent Bolesta, Zwano go kaznodzieją, że zbyt lubił gesta). Teraz ręce przy boku miał, w tył wygiął łokcie, Spod ramion wytknął palce i długie paznokcie, Przedstawiając dwa smycze chartów tym obrazem. Właśnie rzecz kończył: \"Wyczha! puściliśmy razem Ja i Asesor, razem, jakoby dwa kórki Jednym palcem spuszczone u jednej dwórórki; Wyczha! poszli, a zając jak struna - smyk w pole, Psy tuż (to mówiąc, ręce ciągnął wzdłuż po stole I palcami ruch chartów przedziwnie udawał), Psy tuż, i hec! od lasu odsadzili kawał; Sokoł smyk naprzód! rączy pies, lecz zagorzalec, Wysadził się przed Kusym o tyle, o palec; Wiedziałem, że spudłuje; szarak, gracz nie lada, Czchał niby prosto w pole, za nim psów gromada; Gracz szarak! skoro poczuł wszystkie charty w kupie, Pstręk na prawo, koziołka, z nim w prawo psy głupie, A on znowu fajt w lewo, jak wytnie dwa susy, Psy za nim fajt na lewo, on w las, a mój Kusy Cap !!\" - tak krzycząc pan Rejent, na stół pochylony, Z palcami swemi zabiegł aż do drugiej strony I \"cap!\" - Tadeuszowi wrzasnął tuż nad uchem. Tadeusz i sąsiadka, tym głosu wybuchem Znienacka przestraszeni właśnie w pół rozmowy, Odstrychnęli od siebie mimowolnie głowy, Jako wierzchołki drzewa powiązane społem, Gdy je wicher rozerwie; i ręce pod stołem Blisko siebie leżące wstecz nagle uciekły, I dwie twarze w jeden się rumieniec oblekły. Tadeusz, by nie zdradzić swego roztargnienia: \"Prawda\" - rzekł - mój Rejencie, prawda, bez wątpienia, Kusy piękny chart z kształtu, jeśli równie chwytny...\" \"Chwytny? - krzyknął pan Rejent.- Mój pies faworytny Żeby nie miał być chwytny?\" Więc Tadeusz znowu Cieszył się, że tak piękny pies nie ma narowu, Żałował, że go tylko widział idąc z lasu I że przymiotów jego poznać nie miał czasu. Na to zadrżał Asesor, puścił z rąk kieliszek, Utopił w Tadeusza wzrok jak bazyliszek. Asesor mniej krzykliwy i mniej był ruchawy Od Rejenta, szczuplejszy i mały z postawy, Lecz straszny na reducie, balu i sejmiku, Bo powiadano o nim: ma żądło w języku. Tak dowcipne żarciki umiał komponować, Iżby je w kalendarzu można wydrukować: Wszystkie złośliwe, ostre. Dawniej człek dostatni, Schedę ojca swojego i majątek bratni, Wszystko strwonił, na wielkim figurując świecie; Teraz wszedł w służbę rządu, by znaczyć w powiecie. Lubił bardzo myślistwo, już to dla zabawy, Już to że odgłos trąbki i widok obławy Przypominał mu jego lata młodociane, Kiedy miał strzelców licznych i psy zawołane; Teraz mu z całej psiarni dwa charty zostały, I jeszcze z tych jednemu chciano przeczyć chwały. Więc zbliżył się i, z wolna gładząc faworyty, Rzekł z uśmiechem, a był to uśmiech jadowity: \"Chart bez ogona jest jak szlachcic bez urzędu... Ogon też znacznie chartom pomaga do pędu, A Pan kusość uważasz za dowód dobroci? Zresztą zdać się możemy na sąd Pańskiej cioci. Choć pani Telimena mieszkała w stolicy I bawi się niedawno w naszej okolicy, Lepiej zna się na łowach niż myśliwi młodzi: Tak to nauka sama z latami przychodzi\". Tadeusz, na którego niespodzianie spadał Grom taki, wstał zmieszany, chwilę nic nie gadał, Lecz patrzył na rywala coraz straszniéj, srożéj... Wtem, wielkim szczęściem, dwakroć kichnął Podkomorzy. \"Wiwat!\" - krzyknęli wszyscy; on się wszystkim skłonił I z wolna w tabakierę palcami zadzwonił: Tabakiera ze złota, z brylantów oprawa, A w środku jej był portret króla Stanisława. Ojcu Podkomorzego sam król ją darował, Po ojcu Podkomorzy godnie ją piastował; Gdy w nię dzwonił, znak dawał, że miał głos zabierać; Umilkli wszyscy i ust nie śmieli otwierać. On rzekł: \"Wielmożni Szlachta, Bracia Dobrodzieje! Forum myśliwskiem tylko są łąki i knieje, Więc ja w domu podobnych spraw nie decyduję I posiedzenie nasze na jutro solwuję, I dalszych replik stronom dzisiaj nie dozwolę. Woźny! odwołaj sprawę na jutro na pole. Jutro i Hrabia z całym myślistwem tu zjedzie, I Waszeć z nami ruszysz, Sędzio, mój sąsiedzie, I pani Telimena, i panny, i panie, Słowem, zrobim na urząd wielkie polowanie; I Wojski towarzystwa nam też nie odmówi\". To mówiąc tabakierę podawał starcowi. Wojski na ostrym końcu śród myśliwych siedział, Słuchał, zmrużywszy oczy, słowa nie powiedział, Choć młodzież nieraz jego zasięgała zdania, Bo nikt lepiej nad niego nie znał polowania. On milczał, szczyptę wziętą z tabakiery ważył W palcach i długo dumał, nim ją w końcu zażył; Kichnął, aż cała izba rozległa się echem, I potrząsając głową rzekł z gorzkim uśmiechem: \"O, jak mnie to starego i smuci, i dziwi! Cóż by to o tem starzy mówili myśliwi, Widząc, że w tylu szlachty, w tylu panów gronie Mają sądzić się spory o charcim ogonie; Cóż by rzekł na to stary Rejtan, gdyby ożył? Wróciłby do Lachowicz i w grób się położył! Co by rzekł wojewoda Niesiołowski stary, Który ma dotąd pierwsze na świecie ogary I dwiestu strzelców trzyma obyczajem pańskim, I ma sto wozów sieci w zamku worończańskim, A od tylu lat siedzi jak mnich na swym dworze. Nikt go na polowanie uprosić nie może, Białopiotrowiczowi samemu odmówił! Bo cóż by on na waszych polowaniach łowił? Piękna byłaby sława, ażeby pan taki Wedle dzisiejszej mody jeździł na szaraki! Za moich, panie, czasów w języku strzeleckim Dzik, niedźwiedź, łoś, wilk zwany był zwierzem szlacheckim, A zwierzę nie mające kłów, rogów, pazurów Zostawiano dla płatnych sług i dworskich ciurów; Żaden pan nigdy przyjąć nie chciałby do ręki Strzelby, którą zhańbiono, sypiąc w nią śrót cienki! Trzymano wprawdzie chartów, bo z łowów wracając, Trafia się, że spod konia mknie się biedak zając; Puszczano wtenczas za nim dla zabawki smycze I na konikach małe goniły panicze Przed oczami rodziców, którzy te pogonie Ledwie raczyli widzieć, cóż kłócić się o nie! Więc niech Jaśnie Wielmożny Podkomorzy raczy Odwołać swe rozkazy i niech mi wybaczy, Że nie mogę na takie jechać polowanie I nigdy na niem noga moja nie postanie! Nazywam się Hreczecha, a od króla Lecha Żaden za zającami nie jeździł Hreczecha\". Tu śmiech młodzieży mowę Wojskiego zagłuszył. Wstano od stołu; pierwszy Podkomorzy ruszył; Z wieku mu i z urzędu ten zaszczyt należy; Idąc kłaniał się damom, starcom i młodzieży; Za nim szedł kwestarz, Sędzia tuż przy Bernardynie, Sędzia u progu rękę dał Podkomorzynie, Tadeusz Telimenie, Asesor Krajczance, A pan Rejent na końcu Wojskiej Hreczeszance. Tadeusz z kilku gośćmi poszedł do stodoły, A czuł się pomięszany, zły i niewesoły, Rozbierał myślą wszystkie dzisiejsze wypadki: Spotkanie się, wieczerzę przy boku sąsiadki, A szczególniej mu słowo \"ciocia\" koło ucha Brzęczało ciągle jako naprzykrzona mucha. Pragnąłby u Woźnego lepiej się wypytać O pani Telimenie, lecz go nie mógł schwytać; Wojskiego też nie widział, bo zaraz z wieczerzy Wszyscy poszli za gośćmi, jak sługom należy, Urządzając we dworze izby do spoczynku. Starsi i damy spały we dworskim budynku, Młodzież Tadeuszowi prowadzić kazano, W zastępstwie gospodarza, w stodołę na siano. W pół godziny tak było głucho w całym dworze Jako po zadzwonieniu na pacierz w klasztorze; Ciszę przerywał tylko głos nocnego stróża. Usnęli wszyscy. Sędzia sam oczu nie zmruża: Jako wódz gospodarstwa obmyśla wyprawę W pole i w domu przyszłą urządza zabawę. Dał rozkaz ekonomom, wójtom i gumiennym, Pisarzom, ochmistrzyni, strzelcom i stajennym, I musiał wszystkie dzienne rachunki przezierać, Nareszcie rzekł Woźnemu, że się chce rozbierać. Woźny pas mu odwiązał, pas słucki, pas lity, Przy którym świecą gęste kutasy jak kity, Z jednej strony złotogłów w purpurowe kwiaty, Na wywrót jedwab czarny, posrebrzany w kraty; Pas taki można równie kłaść na strony obie: Złotą na dzień galowy, a czarną w żałobie. Sam Woźny umiał pas ten odwiązywać, składać; Właśnie tem się zatrudniał i kończył tak gadać: \"Cóż złego, że przeniosłem stoły do zamczyska? Nikt na tem nic nie stracił, a Pan może zyska, Bo przecież o ten zamek dziś toczy się sprawa. My od dzisiaj do zamku nabyliśmy prawa, I mimo całą strony przeciwnej zajadłość Dowiodę, że zamczysko wzięliśmy w posiadłość. Wszakże kto gości prosi w zamek na wieczerzę, Dowodzi, że posiadłość tam ma albo bierze; Nawet strony przeciwne weźwiemy na świadki: Pamiętam za mych czasów podobne wypadki\". Już Sędzia spał. Więc Woźny cicho wszedł do sieni, Siadł przy świecy i dobył książeczkę z kieszeni, Która mu jak Ołtarzyk złoty zawsze służy, Której nigdy nie rzuca w domu i w podróży. Była to trybunalska wokanda: tam rzędem Stały spisane sprawy, które przed urzędem Woźny sam głosem swoim przed laty wywołał Albo o których później dowiedzieć się zdołał. Prostym ludziom wokanda zda się imion spisem, Woźnemu jest obrazów wspaniałych zarysem. Czytał więc i rozmyślał: Ogiński z Wizgirdem, Dominikanie z Rymszą, Rymsza z Wysogirdem, Radziwiłł z Wereszczaką, Giedrojć z Rodułtowskim, Obuchowicz z kahałem, Juraha z Piotrowskim, Maleski z Mickiewiczem, a na koniec Hrabia Z Soplicą: i czytając, z tych imion wywabia Pamięć spraw wielkich, wszystkie procesu wypadki, I stają mu przed oczy sąd, strony i świadki; I ogląda sam siebie, jak w żupanie białym, W granatowym kontuszu stał przed trybunałem; Jedna ręka na szabli, a drugą do stoła Przywoławszy dwie strony: \"Uciszcie się!\" - woła. Marząc i kończąc pacierz wieczorny, pomału Usnął ostatni w Litwie Woźny trybunału. Takie były zabawy, spory w one lata Śród cichej wsi litewskiej, kiedy reszta świata We łzach i krwi tonęła, gdy ów mąż, bóg wojny, Otoczon chmurą pułków, tysiącem dział zbrojny, Wprzągłszy w swój rydwan orły złote obok srebrnych, Od puszcz libijskich latał do Alpów podniebnych, Ciskając grom po gromie: w Piramidy, w Tabor, W Marengo, w Ulm, w Austerlitz. Zwycięstwo i Zabor Biegły przed nim i za nim. Sława czynów tylu, Brzemienna imionami rycerzy, od Nilu Szła hucząc ku północy, aż u Niemna brzegów Odbiła się, jak od skał, od Moskwy szeregów, Które broniły Litwę murami żelaza Przed wieścią, dla Rosyi straszną jak zaraza. Przecież nieraz nowina, niby kamień z nieba, Spadała w Litwę; nieraz dziad żebrzący chleba, Bez ręki lub bez nogi, przyjąwszy jałmużnę, Stanął i oczy wkoło obracał ostróżne. Gdy nie widział we dworze rosyjskich żołnierzy Ani jarmułek, ani czerwonych kołnierzy, Wtenczas, kim był, wyznawał: był legijonistą, Przynosił kości stare na ziemię ojczystą, Której już bronić nie mógł... Jak go wtenczas cała Rodzina pańska, jak go czeladka ściskała, Zanosząc się od płaczu! On za stołem siadał I dziwniejsze od baśni historyje gadał. On opowiadał, jako jenerał Dąbrowski Z ziemi włoskiej stara się przyciągnąć do Polski, Jak on rodaków zbiera na lombardzkim polu; Jak Kniaziewicz rozkazy daje z Kapitolu I,zwycięzca, wydartych potomkom Cezarów Rzucił w oczy Francuzów sto krwawych sztandarów; Jak Jabłonowski zabiegł, aż kędy pieprz rośnie, Gdzie się cukier wytapia i gdzie w wiecznej wiośnie Pachnące kwitną lasy; z legiją Dunaju Tam wódz Murzyny gromi, a wzdycha do kraju. Mowy starca krążyły we wsi po kryjomu; Chłopiec, co je posłyszał, znikał nagle z domu, Lasami i bagnami skradał się tajemnie, Ścigany od Moskali, skakał kryć się w Niemnie I nurkiem płynął na brzeg Księstwa Warszawskiego, Gdzie usłyszał głos miły: \"Witaj nam, kolego!\" Lecz nim odszedł, wyskoczył na wzgórek z kamienia I Moskalom przez Niemen rzekł: \"Do zobaczenia!\" Tak przekradł się Gorecki, Pac i Obuchowicz, Piotrowski, Obolewski, Rożycki, Janowicz, Mierzejewscy, Brochocki i Bernatowicze, Kupść, Gedymin i inni, których nie policzę; Opuszczali rodziców i ziemię kochaną, I dobra, które na skarb carski zabierano. Czasem do Litwy kwestarz z obcego klasztoru Przyszedł i kiedy bliżej poznał panów dworu, Gazetę im pokazał wyprutą z szkaplerza; Tam stała wypisana i liczba żołnierza, I nazwisko każdego wodza legijonu, I każdego z nich opis zwycięstwa lub zgonu. Po wielu latach pierwszy raz miała rodzina Wieść o życiu, o chwale i o śmierci syna; Brał dom żałobę, ale powiedzieć nie śmiano, Po kim była żałoba, tylko zgadywano W okolicy; i tylko cichy smutek panów Lub cicha radość była gazetą ziemianów. Takim kwestarzem tajnym był Robak podobno: Często on z panem Sędzią rozmawiał osobno; Po tych rozmowach zawsze jakowaś nowina Rozeszła się w sąsiedztwie. Postać Bernardyna Wydawała, że mnich ten nie zawsze w kapturze Chodził i nie w klasztornym zestarzał się murze. Miał on nad prawym uchem, nieco wyżej skroni, Bliznę wyciętej skóry na szerokość dłoni I w brodzie ślad niedawny lancy lub postrzału, Ran tych nie dostał pewnie przy czytaniu mszału. Ale nie tylko groźne wejrzenie i blizny, Lecz sam ruch i głos jego miał coś żołnierszczyzny. Przy mszy, gdy z wzniesionymi zwracał się rękami Od ołtarza do ludu, by mówić:,,Pan z wami\", To nieraz tak się zręcznie skręcił jednym razem, Jakby ,,prawo w tył\" robił za wodza rozkazem, I słowa liturgiji takim wyrzekł tonem Do ludu, jak oficer stojąc przed szwadronem; Postrzegali to chłopcy służący mu do mszy. Spraw także politycznych był Robak świadomszy Niźli żywotów świętych, a jeżdżąc po kweście, Często zastanawiał się w powiatowym mieście; Miał pełno interesów: to listy odbierał, Których nigdy przy obcych ludziach nie otwierał, To wysyłał posłańców, ale gdzie i po co, Nie powiadał; częstokroć wymykał się nocą Do dworów pańskich, z szlachtą ustawicznie szeptał I okoliczne wioski dokoła wydeptał, I w karczmach z wieśniakami rozprawiał niemało, A zawsze o tem, co się w cudzych krajach działo. Teraz Sędziego, który już spał od godziny, Przychodzi budzić; pewnie ma jakieś nowiny.We’re no strangers to love You know the rules and so do I (do I) A full commitment’s what I’m thinking of You wouldn’t get this from any other guy I just wanna tell you how I’m feeling Gotta make you understand Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you We’ve known each other for so long Your heart’s been aching, but you’re too shy to say it (say it) Inside, we both know what’s been going on (going on) We know the game and we’re gonna play it And if you ask me how I’m feeling Don’t tell me you’re too blind to see Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you (Ooh, give you up) (Ooh, give you up) (Ooh) Never gonna give, never gonna give (give you up) (Ooh) Never gonna give, never gonna give (give you up) We’ve known each other for so long Your heart’s been aching, but you’re too shy to say it (to say it) Inside, we both know what’s been going on (going on) We know the game and we’re gonna play it I just wanna tell you how I’m feeling Gotta make you understand Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you “Well, Prince, so Genoa and Lucca are now just family estates of the Buonapartes. But I warn you, if you don’t tell me that this means war, if you still try to defend the infamies and horrors perpetrated by that Antichrist—I really believe he is Antichrist—I will have nothing more to do with you and you are no longer my friend, no longer my ‘faithful slave,’ as you call yourself! But how do you do? I see I have frightened you—sit down and tell me all the news.” It was in July, 1805, and the speaker was the well-known Anna Pávlovna Schérer, maid of honor and favorite of the Empress Márya Fëdorovna. With these words she greeted Prince Vasíli Kurágin, a man of high rank and importance, who was the first to arrive at her reception. Anna Pávlovna had had a cough for some days. She was, as she said, suffering from la grippe; grippe being then a new word in St. Petersburg, used only by the elite. All her invitations without exception, written in French, and delivered by a scarlet-liveried footman that morning, ran as follows: “If you have nothing better to do, Count (or Prince), and if the prospect of spending an evening with a poor invalid is not too terrible, I shall be very charmed to see you tonight between 7 and 10—Annette Schérer.” “Heavens! what a virulent attack!” replied the prince, not in the least disconcerted by this reception. He had just entered, wearing an embroidered court uniform, knee breeches, and shoes, and had stars on his breast and a serene expression on his flat face. He spoke in that refined French in which our grandfathers not only spoke but thought, and with the gentle, patronizing intonation natural to a man of importance who had grown old in society and at court. He went up to Anna Pávlovna, kissed her hand, presenting to her his bald, scented, and shining head, and complacently seated himself on the sofa. “First of all, dear friend, tell me how you are. Set your friend’s mind at rest,” said he without altering his tone, beneath the politeness and affected sympathy of which indifference and even irony could be discerned. “Can one be well while suffering morally? Can one be calm in times like these if one has any feeling?” said Anna Pávlovna. “You are staying the whole evening, I hope?” “And the fete at the English ambassador’s? Today is Wednesday. I must put in an appearance there,” said the prince. “My daughter is coming for me to take me there.” “I thought today’s fete had been canceled. I confess all these festivities and fireworks are becoming wearisome.” “If they had known that you wished it, the entertainment would have been put off,” said the prince, who, like a wound-up clock, by force of habit said things he did not even wish to be believed. “Don’t tease! Well, and what has been decided about Novosíltsev’s dispatch? You know everything.” “What can one say about it?” replied the prince in a cold, listless tone. “What has been decided? They have decided that Buonaparte has burnt his boats, and I believe that we are ready to burn ours.” Prince Vasíli always spoke languidly, like an actor repeating a stale part. Anna Pávlovna Schérer on the contrary, despite her forty years, overflowed with animation and impulsiveness. To be an enthusiast had become her social vocation and, sometimes even when she did not feel like it, she became enthusiastic in order not to disappoint the expectations of those who knew her. The subdued smile which, though it did not suit her faded features, always played round her lips expressed, as in a spoiled child, a continual consciousness of her charming defect, which she neither wished, nor could, nor considered it necessary, to correct. In the midst of a conversation on political matters Anna Pávlovna burst out: “Oh, don’t speak to me of Austria. Perhaps I don’t understand things, but Austria never has wished, and does not wish, for war. She is betraying us! Russia alone must save Europe. Our gracious sovereign recognizes his high vocation and will be true to it. That is the one thing I have faith in! Our good and wonderful sovereign has to perform the noblest role on earth, and he is so virtuous and noble that God will not forsake him. He will fulfill his vocation and crush the hydra of revolution, which has become more terrible than ever in the person of this murderer and villain! We alone must avenge the blood of the just one.... Whom, I ask you, can we rely on?... England with her commercial spirit will not and cannot understand the Emperor Alexander’s loftiness of soul. She has refused to evacuate Malta. She wanted to find, and still seeks, some secret motive in our actions. What answer did Novosíltsev get? None. The English have not understood and cannot understand the self-abnegation of our Emperor who wants nothing for himself, but only desires the good of mankind. And what have they promised? Nothing! And what little they have promised they will not perform! Prussia has always declared that Buonaparte is invincible, and that all Europe is powerless before him.... And I don’t believe a word that Hardenburg says, or Haugwitz either. This famous Prussian neutrality is just a trap. I have faith only in God and the lofty destiny of our adored monarch. He will save Europe!” She suddenly paused, smiling at her own impetuosity. “I think,” said the prince with a smile, “that if you had been sent instead of our dear Wintzingerode you would have captured the King of Prussia’s consent by assault. You are so eloquent. Will you give me a cup of tea?” “In a moment. À propos,” she added, becoming calm again, “I am expecting two very interesting men tonight, le Vicomte de Mortemart, who is connected with the Montmorencys through the Rohans, one of the best French families. He is one of the genuine émigrés, the good ones. And also the Abbé Morio. Do you know that profound thinker? He has been received by the Emperor. Had you heard?” “I shall be delighted to meet them,” said the prince. “But tell me,” he added with studied carelessness as if it had only just occurred to him, though the question he was about to ask was the chief motive of his visit, “is it true that the Dowager Empress wants Baron Funke to be appointed first secretary at Vienna? The baron by all accounts is a poor creature.” Prince Vasíli wished to obtain this post for his son, but others were trying through the Dowager Empress Márya Fëdorovna to secure it for the baron. Anna Pávlovna almost closed her eyes to indicate that neither she nor anyone else had a right to criticize what the Empress desired or was pleased with. “Baron Funke has been recommended to the Dowager Empress by her sister,” was all she said, in a dry and mournful tone. As she named the Empress, Anna Pávlovna’s face suddenly assumed an expression of profound and sincere devotion and respect mingled with sadness, and this occurred every time she mentioned her illustrious patroness. She added that Her Majesty had deigned to show Baron Funke beaucoup d’estime, and again her face clouded over with sadness. The prince was silent and looked indifferent. But, with the womanly and courtierlike quickness and tact habitual to her, Anna Pávlovna wished both to rebuke him (for daring to speak as he had done of a man recommended to the Empress) and at the same time to console him, so she said: “Now about your family. Do you know that since your daughter came out everyone has been enraptured by her? They say she is amazingly beautiful.” The prince bowed to signify his respect and gratitude. “I often think,” she continued after a short pause, drawing nearer to the prince and smiling amiably at him as if to show that political and social topics were ended and the time had come for intimate conversation—“I often think how unfairly sometimes the joys of life are distributed. Why has fate given you two such splendid children? I don’t speak of Anatole, your youngest. I don’t like him,” she added in a tone admitting of no rejoinder and raising her eyebrows. “Two such charming children. And really you appreciate them less than anyone, and so you don’t deserve to have them.” And she smiled her ecstatic smile. “I can’t help it,” said the prince. “Lavater would have said I lack the bump of paternity.” “Don’t joke; I mean to have a serious talk with you. Do you know I am dissatisfied with your younger son? Between ourselves” (and her face assumed its melancholy expression), “he was mentioned at Her Majesty’s and you were pitied....” The prince answered nothing, but she looked at him significantly, awaiting a reply. He frowned. “What would you have me do?” he said at last. “You know I did all a father could for their education, and they have both turned out fools. Hippolyte is at least a quiet fool, but Anatole is an active one. That is the only difference between them.” He said this smiling in a way more natural and animated than usual, so that the wrinkles round his mouth very clearly revealed something unexpectedly coarse and unpleasant. “And why are children born to such men as you? If you were not a father there would be nothing I could reproach you with,” said Anna Pávlovna, looking up pensively. “I am your faithful slave and to you alone I can confess that my children are the bane of my life. It is the cross I have to bear. That is how I explain it to myself. It can’t be helped!” He said no more, but expressed his resignation to cruel fate by a gesture. Anna Pávlovna meditated. “Have you never thought of marrying your prodigal son Anatole?” she asked. “They say old maids have a mania for matchmaking, and though I don’t feel that weakness in myself as yet, I know a little person who is very unhappy with her father. She is a relation of yours, Princess Mary Bolkónskaya.” Prince Vasíli did not reply, though, with the quickness of memory and perception befitting a man of the world, he indicated by a movement of the head that he was considering this information. “Do you know,” he said at last, evidently unable to check the sad current of his thoughts, “that Anatole is costing me forty thousand rubles a year? And,” he went on after a pause, “what will it be in five years, if he goes on like this?” Presently he added: “That’s what we fathers have to put up with.... Is this princess of yours rich?” “Her father is very rich and stingy. He lives in the country. He is the well-known Prince Bolkónski who had to retire from the army under the late Emperor, and was nicknamed ‘the King of Prussia.’ He is very clever but eccentric, and a bore. The poor girl is very unhappy. She has a brother; I think you know him, he married Lise Meinen lately. He is an aide-de-camp of Kutúzov’s and will be here tonight.” “Listen, dear Annette,” said the prince, suddenly taking Anna Pávlovna’s hand and for some reason drawing it downwards. “Arrange that affair for me and I shall always be your most devoted slave-slafe with an f, as a village elder of mine writes in his reports. She is rich and of good family and that’s all I want.” And with the familiarity and easy grace peculiar to him, he raised the maid of honor’s hand to his lips, kissed it, and swung it to and fro as he lay back in his armchair, looking in another direction. “Attendez,” said Anna Pávlovna, reflecting, “I’ll speak to Lise, young Bolkónski’s wife, this very evening, and perhaps the thing can be arranged. It shall be on your family’s behalf that I’ll start my apprenticeship as old maid.” CHAPTER II Anna Pávlovna’s drawing room was gradually filling. The highest Petersburg society was assembled there: people differing widely in age and character but alike in the social circle to which they belonged. Prince Vasíli’s daughter, the beautiful Hélène, came to take her father to the ambassador’s entertainment; she wore a ball dress and her badge as maid of honor. The youthful little Princess Bolkónskaya, known as la femme la plus séduisante de Pétersbourg, * was also there. She had been married during the previous winter, and being pregnant did not go to any large gatherings, but only to small receptions. Prince Vasíli’s son, Hippolyte, had come with Mortemart, whom he introduced. The Abbé Morio and many others had also come. To each new arrival Anna Pávlovna said, “You have not yet seen my aunt,” or “You do not know my aunt?” and very gravely conducted him or her to a little old lady, wearing large bows of ribbon in her cap, who had come sailing in from another room as soon as the guests began to arrive; and slowly turning her eyes from the visitor to her aunt, Anna Pávlovna mentioned each one’s name and then left them. Each visitor performed the ceremony of greeting this old aunt whom not one of them knew, not one of them wanted to know, and not one of them cared about; Anna Pávlovna observed these greetings with mournful and solemn interest and silent approval. The aunt spoke to each of them in the same words, about their health and her own, and the health of Her Majesty, “who, thank God, was better today.” And each visitor, though politeness prevented his showing impatience, left the old woman with a sense of relief at having performed a vexatious duty and did not return to her the whole evening. The young Princess Bolkónskaya had brought some work in a gold-embroidered velvet bag. Her pretty little upper lip, on which a delicate dark down was just perceptible, was too short for her teeth, but it lifted all the more sweetly, and was especially charming when she occasionally drew it down to meet the lower lip. As is always the case with a thoroughly attractive woman, her defect—the shortness of her upper lip and her half-open mouth—seemed to be her own special and peculiar form of beauty. Everyone brightened at the sight of this pretty young woman, so soon to become a mother, so full of life and health, and carrying her burden so lightly. Old men and dull dispirited young ones who looked at her, after being in her company and talking to her a little while, felt as if they too were becoming, like her, full of life and health. All who talked to her, and at each word saw her bright smile and the constant gleam of her white teeth, thought that they were in a specially amiable mood that day. The little princess went round the table with quick, short, swaying steps, her workbag on her arm, and gaily spreading out her dress sat down on a sofa near the silver samovar, as if all she was doing was a pleasure to herself and to all around her. “I have brought my work,” said she in French, displaying her bag and addressing all present. “Mind, Annette, I hope you have not played a wicked trick on me,” she added, turning to her hostess. “You wrote that it was to be quite a small reception, and just see how badly I am dressed.” And she spread out her arms to show her short-waisted, lace-trimmed, dainty gray dress, girdled with a broad ribbon just below the breast.": 42 } +''' + +# Define queries to test on the input. +[[queries]] + # Valid JSONPath query string. +query = "$..['This is meant to force the buffered input to have to read ahead to actually compare the entire key. Since we use a very big buffer size (64KiB) we need this long of a label to actually test that code. It is unlikely anyone ever has JSON keys that are *this* long, but hey, correctness is correctness. Litwo! Ojczyzno moja! ty jesteś jak zdrowie. Ile cię trzeba cenić, ten tylko się dowie, Kto cię stracił. Dziś piękność twą w całej ozdobie Widzę i opisuję, bo tęsknię po tobie. Panno Święta, co Jasnej bronisz Częstochowy I w Ostrej świecisz Bramie! Ty, co gród zamkowy Nowogródzki ochraniasz z jego wiernym ludem! Jak mnie dziecko do zdrowia powróciłaś cudem (Gdy od płaczącej matki pod Twoję opiekę Ofiarowany, martwą podniosłem powiekę I zaraz mogłem pieszo do Twych świątyń progu Iść za wrócone życie podziękować Bogu), Tak nas powrócisz cudem na Ojczyzny łono. Tymczasem przenoś moję duszę utęsknioną Do tych pagórków leśnych, do tych łąk zielonych, Szeroko nad błękitnym Niemnem rozciągnionych; Do tych pól malowanych zbożem rozmaitem, Wyzłacanych pszenicą, posrebrzanych żytem; Gdzie bursztynowy świerzop, gryka jak śnieg biała, Gdzie panieńskim rumieńcem dzięcielina pała, A wszystko przepasane, jakby wstęgą, miedzą Zieloną, na niej z rzadka ciche grusze siedzą. Śród takich pól przed laty, nad brzegiem ruczaju, Na pagórku niewielkim, we brzozowym gaju, Stał dwór szlachecki, z drzewa, lecz podmurowany; Świeciły się z daleka pobielane ściany, Tem bielsze, że odbite od ciemnej zieleni Topoli, co go bronią od wiatrów jesieni. Dóm mieszkalny niewielki, lecz zewsząd chędogi, I stodołę miał wielką, i przy niej trzy stogi Użątku, co pod strzechą zmieścić się nie może; Widać, że okolica obfita we zboże, I widać z liczby kopic, co wzdłuż i wszerz smugów Świecą gęsto jak gwiazdy, widać z liczby pługów Orzących wcześnie łany ogromne ugoru, Czarnoziemne, zapewne należne do dworu, Uprawne dobrze na kształt ogrodowych grządek: Że w tym domu dostatek mieszka i porządek. Brama na wciąż otwarta przechodniom ogłasza, Że gościnna i wszystkich w gościnę zaprasza. Właśnie dwókonną bryką wjechał młody panek I obiegłszy dziedziniec zawrócił przed ganek, Wysiadł z powozu; konie porzucone same, Szczypiąc trawę ciągnęły powoli pod bramę. We dworze pusto, bo drzwi od ganku zamknięto Zaszczepkami i kołkiem zaszczepki przetknięto. Podróżny do folwarku nie biegł sług zapytać; Odemknął, wbiegł do domu, pragnął go powitać. Dawno domu nie widział, bo w dalekim mieście Kończył nauki, końca doczekał nareszcie. Wbiega i okiem chciwie ściany starodawne Ogląda czule, jako swe znajome dawne. Też same widzi sprzęty, też same obicia, Z któremi się zabawiać lubił od powicia; Lecz mniej wielkie, mniej piękne, niż się dawniej zdały. I też same portrety na ścianach wisiały. Tu Kościuszko w czamarce krakowskiej, z oczyma Podniesionymi w niebo, miecz oburącz trzyma; Takim był, gdy przysięgał na stopniach ołtarzów, Że tym mieczem wypędzi z Polski trzech mocarzów Albo sam na nim padnie. Dalej w polskiej szacie Siedzi Rejtan żałośny po wolności stracie, W ręku trzymna nóż, ostrzem zwrócony do łona, A przed nim leży Fedon i żywot Katona. Dalej Jasiński, młodzian piękny i posępny, Obok Korsak, towarzysz jego nieodstępny, Stoją na szańcach Pragi, na stosach Moskali, Siekąc wrogów, a Praga już się wkoło pali. Nawet stary stojący zegar kurantowy W drewnianej szafie poznał u wniścia alkowy I z dziecinną radością pociągnął za sznurek, By stary Dąbrowskiego usłyszyć mazurek. Biegał po całym domu i szukał komnaty, Gdzie mieszkał, dzieckiem będąc, przed dziesięciu laty. Wchodzi, cofnął się, toczył zdumione źrenice Po ścianach: w tej komnacie mieszkanie kobiéce? Któż by tu mieszkał? stary stryj nie był żonaty, A ciotka w Petersburgu mieszkała przed laty. To nie był ochmistrzyni pokój! Fortepiano? Na niem noty i książki; wszystko porzucano Niedbale i bezładnie; nieporządek miły! Niestare były rączki, co je tak rzuciły. Tuż i sukienka biała, świeżo z kołka zdjęta Do ubrania, na krzesła poręczu rozpięta. A na oknach donice z pachnącemi ziołki, Geranium, lewkonija, astry i fijołki. Podróżny stanął w jednym z okien - nowe dziwo: W sadzie, na brzegu niegdyś zarosłym pokrzywą, Był maleńki ogródek, ścieżkami porznięty, Pełen bukietów trawy angielskiej i mięty. Drewniany, drobny, w cyfrę powiązany płotek Połyskał się wstążkami jaskrawych stokrotek. Grządki widać, że były świeżo polewane; Tuż stało wody pełne naczynie blaszane, Ale nigdzie nie widać było ogrodniczki; Tylko co wyszła; jeszcze kołyszą się drzwiczki Świeżo trącone; blisko drzwi ślad widać nóżki Na piasku: bez trzewika była i pończoszki; Na piasku drobnym, suchym, białym na kształt śniegu, Ślad wyraźny, lecz lekki; odgadniesz, że w biegu Chybkim był zostawiony nóżkami drobnemi Od kogoś, co zaledwie dotykał się ziemi. Podróżny długo w oknie stał patrząc, dumając, Wonnemi powiewami kwiatów oddychając, Oblicze aż na krzaki fijołkowe skłonił, Oczyma ciekawemi po drożynach gonił I znowu je na drobnych śladach zatrzymywał, Myślał o nich i, czyje były, odgadywał. Przypadkiem oczy podniosł, i tu na parkanie Stała młoda dziewczyna. - Białe jej ubranie Wysmukłą postać tylko aż do piersi kryje, Odsłaniając ramiona i łabędzią szyję. W takiem Litwinka tylko chodzić zwykła z rana, W takiem nigdy nie bywa od mężczyzn widziana: Więc choć świadka nie miała, założyła ręce Na piersiach, przydawając zasłony sukience. Włos w pukle nierozwity, lecz w węzełki małe Pokręcony, schowany w drobne strączki białe, Dziwnie ozdabiał głowę, bo od słońca blasku Świecił się, jak korona na świętych obrazku. Twarzy nie było widać. Zwrócona na pole Szukała kogoś okiem, daleko, na dole; Ujrzała, zaśmiała się i klasnęła w dłonie, Jak biały ptak zleciała z parkanu na błonie I wionęła ogrodem przez płotki, przez kwiaty, I po desce opartej o ścianę komnaty, Nim spostrzegł się, wleciała przez okno, świecąca, Nagła, cicha i lekka jak światłość miesiąca. Nócąc chwyciła suknie, biegła do zwierciadła; Wtem ujrzała młodzieńca i z rąk jej wypadła Suknia, a twarz od strachu i dziwu pobladła. Twarz podróżnego barwą spłonęła rumianą Jak obłok, gdy z jutrzenką napotka się ranną; Skromny młodzieniec oczy zmrużył i przysłonił, Chciał coś mówić, przepraszać, tylko się ukłonił I cofnął się; dziewica krzyknęła boleśnie, Niewyraźnie, jak dziecko przestraszone we śnie; Podróżny zląkł się, spójrzał, lecz już jej nie było, Wyszedł zmieszany i czuł, że serce mu biło Głośno, i sam nie wiedział, czy go miało śmieszyć To dziwaczne spotkanie, czy wstydzić, czy cieszyć. Tymczasem na folwarku nie uszło baczności, Że przed ganek zajechał któryś z nowych gości. Już konie w stajnię wzięto, już im hojnie dano Jako w porządnym domu, i obrok, i siano; Bo Sędzia nigdy nie chciał, według nowej mody, Odsyłać konie gości Żydom do gospody. Słudzy nie wyszli witać, ale nie myśl wcale, Aby w domu Sędziego służono niedbale; Słudzy czekają, nim się pan Wojski ubierze, Który teraz za domem urządzał wieczerzę. On Pana zastępuje i on w niebytności Pana, zwykł sam przyjmować i zabawiać gości (Daleki krewny pański i przyjaciel domu). Widząc gościa, na folwark dążył po kryjomu (Bo nie mógł wyjść spotykać w tkackim pudermanie); Wdział więc, jak mógł najprędzej, niedzielne ubranie, Nagotowane z rana, bo od rana wiedział, Że u wieczerzy będzie z mnóstwem gości siedział. Pan Wojski poznał z dala, ręce rozkrzyżował I z krzykiem podróżnego ściskał i całował; Zaczęła się ta prędka, zmieszana rozmowa, W której lat kilku dzieje chciano zamknąć w słowa Krótkie i poplątane, w ciąg powieści, pytań, Wykrzykników i westchnień, i nowych powitań. Gdy się pan Wojski dosyć napytał, nabadał, Na samym końcu dzieje tego dnia powiadał: \"Dobrze, mój Tadeuszu (bo tak nazywano Młodzieńca, który nosił Kościuszkowskie miano Na pamiątkę, że w czasie wojny się urodził), Dobrze, mój Tadeuszu, żeś się dziś nagodził Do domu, właśnie kiedy mamy panien wiele. Stryjaszek myśli wkrótce sprawić ci wesele; Jest z czego wybrać; u nas towarzystwo liczne Od dni kilku zbiera się na sądy graniczne Dla skończenia dawnego z panem Hrabią sporu; I pan Hrabia ma jutro sam zjechać do dworu; Podkomorzy już zjechał z żoną i z córkami. Młodzież poszła do lasu bawić się strzelbami, A starzy i kobiety żniwo oglądają Pod lasem, i tam pewnie na młodzież czekają. Pójdziemy, jeśli zechcesz, i wkrótce spotkamy Stryjaszka, Podkomorstwo i szanowne damy\". Pan Wojski z Tadeuszem idą pod las drogą I jeszcze się do woli nagadać nie mogą. Słońce ostatnich kresów nieba dochodziło, Mniej silnie, ale szerzej niż we dnie świeciło, Całe zaczerwienione, jak zdrowe oblicze Gospodarza, gdy prace skończywszy rolnicze, Na spoczynek powraca. Już krąg promienisty Spuszcza się na wierzch boru i już pomrok mglisty, Napełniając wierzchołki i gałęzie drzewa, Cały las wiąże w jedno i jakoby zlewa; I bór czernił się na kształt ogromnego gmachu, Słońce nad nim czerwone jak pożar na dachu; Wtem zapadło do głębi; jeszcze przez konary Błysnęło jako świeca przez okienic szpary I zgasło. I wnet sierpy gromadnie dzwoniące We zbożach i grabliska suwane po łące Ucichły i stanęły: tak pan Sędzia każe, U niego ze dniem kończą prace gospodarze. \"Pan świata wie, jak długo pracować potrzeba; Słońce, Jego robotnik, kiedy znidzie z nieba, Czas i ziemianinowi ustępować z pola\". Tak zwykł mawiać pan Sędzia; a Sędziego wola Była ekonomowi poczciwemu świętą; Bo nawet wozy, w które już składać zaczęto Kopę żyta, niepełne jadą do stodoły; Cieszą się z niezwyczajnej ich lekkości woły. Właśnie z lasu wracało towarzystwo całe Wesoło, lecz w porządku; naprzód dzieci małe Z dozorcą, potem Sędzia szedł z Podkomorzyną, Obok pan Podkomorzy otoczon rodziną; Panny tuż za starszemi, a młodzież na boku; Panny szły przed młodzieżą o jakie pół kroku (Tak każe przyzwoitość); nikt tam nie rozprawiał O porządku, nikt mężczyzn i dam nie ustawiał, A każdy mimowolnie porządku pilnował. Bo Sędzia w domu dawne obyczaje chował I nigdy nie dozwalał, by chybiano względu Dla wieku, urodzenia, rozumu, urzędu. \"Tym ładem- mawiał - domy i narody słyną, Z jego upadkiem domy i narody giną\". Więc do porządku wykli domowi i słudzy; I przyjezdny gość, krewny albo człowiek cudzy, Gdy Sędziego nawiedził, skoro pobył mało, Przejmował zwyczaj, którym wszystko oddychało. Krótkie były Sędziego z synowcem witania: Dał mu poważnie rękę do pocałowania I w skroń ucałowawszy, uprzejmie pozdrowił; A choć przez wzgląd na gości niewiele z nim mówił, Widać było z łez, które wylotem kontusza Otarł prędko, jak kochał pana Tadeusza. W ślad gospodarza wszystko ze żniwa i z boru, I z łąk, i z pastwisk razem wracało do dworu. Tu owiec trzoda becząc w ulice się tłoczy I wznosi chmurę pyłu; dalej z wolna kroczy Stado cielic tyrolskich z mosiężnemi dzwonki; Tam konie rżące lecą ze skoszonej łąki; Wszystko bieży ku studni, której ramię z drzewa Raz w raz skrzypi i napój w koryta rozlewa. Sędzia, choć utrudzony, chociaż w gronie gości, Nie chybił gospodarskiej ważnej powinności: Udał się sam ku studni; najlepiej z wieczora Gospodarz widzi, w jakim stanie jest obora; Dozoru tego nigdy sługom nie poruczy, Bo Sędzia wie, że oko pańskie konia tuczy. Wojski z woźnym Protazym ze świecami w sieni Stali i rozprawiali, nieco poróżnieni; Bo w niebytność Wojskiego Woźny po kryjomu Kazał stoły z wieczerzą powynosić z domu I ustawić co prędzej w pośrodku zamczyska, Którego widne były pod lasem zwaliska. Po cóż te przenosiny? Pan Wojski się krzywił I przepraszał Sędziego; Sędzia się zadziwił, Lecz stało się; już późno i trudno zaradzić, Wolał gości przeprosić i w pustki wprowadzić. Po drodze Woźny ciągle Sędziemu tłumaczył, Dlaczego urządzenie pańskie przeinaczył: We dworze żadna izba nie ma obszerności Dostatecznej dla tylu, tak szanownych gości; W zamku sień wielka, jeszcze dobrze zachowana, Sklepienie całe - wprawdzie pękła jedna ściana, Okna bez szyb, lecz latem nic to nie zawadzi; Bliskość piwnic wygodna służącej czeladzi. Tak mówiąc, na Sędziego mrugał; widać z miny, Że miał i taił inne, ważniejsze przyczyny. O dwa tysiące kroków zamek stał za domem, Okazały budową, poważny ogromem, Dziedzictwo starożytnej rodziny Horeszków; Dziedzic zginął był w czasie krajowych zamieszków. Dobra, całe zniszczone sekwestrami rządu, Bezładnością opieki, wyrokami sądu, W cząstce spadły dalekim krewnym po kądzieli, A resztę rozdzielono między wierzycieli. Zamku żaden wziąść nie chciał, bo w szlacheckim stanie Trudno było wyłożyć koszt na utrzymanie; Lecz Hrabia, sąsiad bliski, gdy wyszedł z opieki, Panicz bogaty, krewny Horeszków daleki, Przyjechawszy z wojażu upodobał mury, Tłumacząc, że gotyckiej są architektury; Choć Sędzia z dokumentów przekonywał o tem, Że architekt był majstrem z Wilna, nie zaś Gotem. Dość, że Hrabia chciał zamku, właśnie i Sędziemu Przyszła nagle taż chętka, nie wiadomo czemu. Zaczęli proces w ziemstwie, potem w głównym sądzie, W senacie, znowu w ziemstwie i w guberskim rządzie; Wreszcie po wielu kosztach i ukazach licznych Sprawa wróciła znowu do sądów granicznych. Słusznie Woźny powiadał, że w zamkowej sieni Zmieści się i palestra, i goście proszeni. Sień wielka jak refektarz, z wypukłym sklepieniem Na filarach, podłoga wysłana kamieniem, Ściany bez żadnych ozdób, ale mur chędogi; Sterczały wkoło sarnie i jelenie rogi Z napisami: gdzie, kiedy te łupy zdobyte; Tuż myśliwców herbowne klejnoty wyryte I stoi wypisany każdy po imieniu; Herb Horeszków, Półkozic, jaśniał na sklepieniu. Goście weszli w porządku i stanęli kołem; Podkomorzy najwyższe brał miejsce za stołem; Z wieku mu i z urzędu ten zaszczyt należy. Idąc kłaniał się damom, starcom i młodzieży. Przy nim stał Kwestarz, Sędzia tuż przy Bernardynie. Bernardyn zmówił krótki pacierz po łacinie. Mężczyznom dano wódkę; wtenczas wszyscy siedli I chołodziec litewski milcząc żwawo jedli. Pan Tadeusz, choć młodzik, ale prawem gościa Wysoko siadł przy damach obok Jegomościa; Między nim i stryjaszkiem jedno pozostało Puste miejsce, jak gdyby na kogoś czekało. Stryj nieraz na to miejsce i na drzwi poglądał, Jakby czyjegoś przyjścia był pewny i żądał. I Tadeusz wzrok stryja ku drzwiom odprowadzał, I z nim na miejscu pustym oczy swe osadzał. Dziwna rzecz! Miejsca wkoło są siedzeniem dziewic, Na które mógłby spojrzeć bez wstydu królewic, Wszystkie zacnie zrodzone, każda młoda, ładna; Tadeusz tam pogląda, gdzie nie siedzi żadna. To miejsce jest zagadką, młódź lubi zagadki; Roztargniony, do swojej nadobnej sąsiadki Ledwo słów kilka wyrzekł, do Podkomorzanki; Nie zmienia jej talerzów, nie nalewa szklanki, I panien nie zabawia przez rozmowy grzeczne, Z których by wychowanie poznano stołeczne; To jedno puste miejsce nęci go i mami... Już nie puste, bo on je napełnił myślami. Po tem miejscu biegało domysłów tysiące, Jako po deszczu żabki po samotnej łące; Śród nich jedna króluje postać, jak w pogodę Lilia jeziór skroń białą wznosząca nad wodę. Dano trzecią potrawę. Wtem pan Podkomorzy, Wlawszy kropelkę wina w szklankę panny Róży, A młodszej przysunąwszy z talerzem ogórki, Rzekł: \"Muszę ja wam służyć, moje panny córki, Choć stary i niezgrabny\". Zatem się rzuciło Kilku młodych od stołu i pannom służyło. Sędzia, z boku rzuciwszy wzrok na Tadeusza I poprawiwszy nieco wylotów kontusza, Nalał węgrzyna i rzekł: \"Dziś nowym zwyczajem, My na naukę młodzież do stolicy dajem I nie przeczym, że nasi synowie i wnuki Mają od starych więcej książkowej nauki; Ale co dzień postrzegam, jak młódź cierpi na tem, Że nie ma szkół uczących żyć z ludźmi i światem. Dawniej na dwory pańskie jachał szlachcic młody, Ja sam lat dziesięć byłem dworskim Wojewody, Ojca Podkomorzego, Mościwego Pana (Mówiąc, Podkomorzemu ścisnął za kolana); On mnie radą do usług publicznych sposobił, Z opieki nie wypuścił, aż człowiekiem zrobił. W mym domu wiecznie będzie jego pamięć droga, Co dzień za duszę jego proszę Pana Boga. Jeślim tyle na jego nie korzystał dworze Jak drudzy i wróciwszy w domu ziemię orzę, Gdy inni, więcej godni Wojewody względów, Doszli potem najwyższych krajowych urzędów, Przynajmniej tom skorzystał, że mi w moim domu Nikt nigdy nie zarzuci, bym uchybił komu W uczciwości, w grzeczności; a ja powiem śmiało: Grzeczność nie jest nauką łatwą ani małą. Niełatwą, bo nie na tym kończy się, jak nogą Zręcznie wierzgnąć, z uśmiechem witać lada kogo; Bo taka grzeczność modna zda mi się kupiecka, Ale nie staropolska, ani też szlachecka. Grzeczność wszystkim należy, lecz każdemu inna; Bo nie jest bez grzeczności i miłość dziecinna, I wzgląd męża dla żony przy ludziach, i pana Dla sług swoich, a w każdej jest pewna odmiana. Trzeba się długo uczyć, ażeby nie zbłądzić I każdemu powinną uczciwość wyrządzić. I starzy się uczyli; u panów rozmowa Była to historyja żyjąca krajowa, A między szlachtą dzieje domowe powiatu: Dawano przez to poznać szlachcicowi bratu, Że wszyscy o nim wiedzą, lekce go nie ważą; Więc szlachcic obyczaje swe trzymał pod strażą. Dziś człowieka nie pytaj: co zacz? kto go rodzi? Z kim on żył, co porabiał? każdy, gdzie chce, wchodzi, Byle nie szpieg rządowy i byle nie w nędzy. Jak ów Wespazyjanus nie wąchał pieniędzy I nie chciał wiedzieć, skąd są, z jakich rąk i krajów, Tak nie chcą znać człowieka rodu, obyczajów! Dość, że ważny i że się stempel na nim widzi, Więc szanują przyjaciół jak pieniądze Żydzi\". To mówiąc Sędzia gości obejrzał porządkiem; Bo choć zawsze i płynnie mówił, i z rozsądkiem, Wiedział, że niecierpliwa młodzież teraźniejsza, Że ją nudzi rzecz długa, choć najwymowniejsza. Ale wszyscy słuchali w milczeniu głębokiem; Sędzia Podkomorzego zdał się radzić okiem, Podkomorzy pochwałą rzeczy nie przerywał, Ale częstem skinieniem głowy potakiwał. Sędzia milczał, on jeszcze skinieniem przyzwalał; Więc Sędzia jego puchar i swój kielich nalał I dalej mówił: \"Grzeczność nie jest rzeczą małą: Kiedy się człowiek uczy ważyć, jak przystało, Drugich wiek, urodzenie, cnoty, obyczaje, Wtenczas i swoją ważność zarazem poznaje; Jak na szalach żebyśmy nasz ciężar poznali, Musim kogoś posadzić na przeciwnej szali. Zaś godna jest Waszmościów uwagi osobnej Grzeczność, którą powinna młodź dla płci nadobnej ; Zwłaszcza gdy zacność domu, fortuny szczodroty Objaśniają wrodzone wdzięki i przymioty. Stąd droga do afektów i stąd się kojarzy Wspaniały domów sojusz - tak myślili starzy. A zatem...\" Tu Pan Sędzia nagłym zwrotem głowy Skinął na Tadeusza, rzucił wzrok surowy, Znać było, że przychodził już do wniosków mowy. Wtem brząknął w tabakierkę złotą Podkomorzy I rzekł: \"Mój Sędzio, dawniej było jeszcze gorzéj! Teraz nie wiem, czy moda i nas starych zmienia, Czy młodzież lepsza, ale widzę mniej zgorszenia. Ach, ja pamiętam czasy, kiedy do Ojczyzny Pierwszy raz zawitała moda francuszczyzny! Gdy raptem paniczyki młode z cudzych krajów Wtargnęli do nas hordą gorszą od Nogajów! Prześladując w Ojczyźnie Boga, przodków wiarę, Prawa i obyczaje, nawet suknie stare. Żałośnie było widzieć wyżółkłych młokosów, Gadających przez nosy, a często bez nosów, Opatrzonych w broszurki i w różne gazety, Głoszących nowe wiary, prawa, toalety. Miała nad umysłami wielką moc ta tłuszcza; Bo Pan Bóg, kiedy karę na naród przepuszcza, Odbiera naprzód rozum od obywateli. I tak mędrsi fircykom oprzeć się nie śmieli; I zląkł ich się jak dżumy jakiej cały naród, Bo już sam wewnątrz siebie czuł choroby zaród. Krzyczano na modnisiów, a brano z nich wzory: Zmieniano wiarę, mowę, prawa i ubiory. Była to maszkarada, zapustna swawola, Po której miał przyjść wkrótce wielki post - niewola! Pamiętam, chociaż byłem wtenczas małe dziecię, Kiedy do ojca mego w oszmiańskim powiecie Przyjechał pan Podczaszyc na francuskim wózku, Pierwszy człowiek, co w Litwie chodził po francusku. Biegali wszyscy za nim jakby za rarogiem, Zazdroszczono domowi, przed którego progiem Stanęła Podczaszyca dwukolna dryndulka, Która się po francusku zwała karyjulka. Zamiast lokajów w kielni siedziały dwa pieski, A na kozłach niemczysko chude na kształt deski; Nogi miał długie, cienkie, jak od chmielu tyki, W pończochach, ze srebrnemi klamrami trzewiki, Peruka z harbajtelem zawiązanym w miechu. Starzy na on ekwipaż parskali ze śmiechu, A chłopi żegnali się, mowiąc, że po świecie Jeździ wenecki diabeł w niemieckiej karecie. Sam Podczaszyc jaki był, opisywać długo; Dosyć, że się nam zdawał małpą lub papugą, W wielkiej peruce, którą do złotego runa On lubił porównywać, a my do kołtuna. Jeśli kto i czuł wtenczas, że polskie ubranie Piękniejsze jest niż obcej mody małpowanie, Milczał; boby krzyczała młodzież, że przeszkadza Kulturze, że tamuje progresy, że zdradza! Taka była przesądów owoczesnych władza! \"Podczaszyc zapowiedział, że nas reformować, Cywilizować będzie i konstytuować; Ogłosił nam, że jacyś Francuzi wymowni Zrobili wynalazek, iż ludzie są rowni. Choć o tym dawno w Pańskim pisano zakonie I każdy ksiądz toż samo gada na ambonie. Nauka dawną była, szło o jej pełnienie! Lecz wtenczas panowało takie oślepienie, Że nie wierzono rzeczom najdawniejszym w świecie, Jeśli ich nie czytano w francuskiej gazecie. Podczaszyc, mimo równość, wziął tytuł markiża; Wiadomo, że tytuły przychodzą z Paryża, A natenczas tam w modzie był tytuł markiża. Jakoż, kiedy się moda odmieniła z laty, Tenże sam markiż przybrał tytuł demokraty; Wreszcie z odmienną modą, pod Napoleonem, Demokrata przyjechał z Paryża baronem; Gdyby żył dłużej, może nową alternatą Z barona przechrzciłby się kiedyś demokratą. Bo Paryż częstą mody odmianą się chlubi, A co Francuz wymyśli, to Polak polubi. Chwała Bogu, że teraz jeśli nasza młodzież Wyjeżdża za granicę, to już nie po odzież, Nie szukać prawodawstwa w drukarskich kramarniach Lub wymowy uczyć się w paryskich kawiarniach. Bo teraz Napoleon, człek mądry a prędki, Nie daje czasu szukać mody i gawędki. Teraz grzmi oręż, a nam starym serca rosną, Że znowu o Polakach tak na świecie głośno; Jest sława, a więc będzie i Rzeczpospolita! Zawżdy z wawrzynów drzewo wolności wykwita. Tylko smutno, że nam, ach! tak się lata wleką W nieczynności! a oni tak zawsze daleko! Tak długo czekać! Nawet tak rzadka nowina! Ojcze Robaku (ciszej rzekł do Bernardyna), Słyszałem, żeś zza Niemna odebrał wiadomość; Może też co o naszym wojsku wie Jegomość?\" \"Nic a nic - odpowiedział Robak obojętnie (Widać było, że słuchał rozmowy niechętnie) - Mnie polityka nudzi; jeżeli z Warszawy Mam list, to rzecz zakonna, to są nasze sprawy Bernardyńskie; cóż o tem gadać u wieczerzy? Są tu świeccy, do których nic to nie należy\". Tak mowiąc spojrzał zyzem, gdzie śród biesiadników Siedział gość Moskal; był to pan kapitan Ryków; Stary żołnierz, stał w bliskiej wiosce na kwaterze, Pan Sędzia go przez grzeczność prosił na wieczerzę. Rykow jadł smaczno, mało wdawał się w rozmowę, Lecz na wzmiankę Warszawy rzekł, podniosłszy głowę: \"Pan Podkomorzy! Oj, Wy! Pan zawsze ciekawy O Bonaparta, zawsze Wam tam do Warszawy! He! Ojczyzna! Ja nie szpieg, a po polsku umiem - Ojczyzna! Ja to czuję wszystko, ja rozumiem! Wy Polaki, ja Ruski, teraz się nie bijem, Jest armistycjum, to my razem jemy, pijem. Często na awanpostach nasz z Francuzem gada, Pije wódkę; jak krzykną: ura! - kanonada. Ruskie przysłowie: Z kim się biję, tego lubię; Gładź drużkę jak po duszy, a bij jak po szubie. Ja mówię, będzie wojna u nas. Do majora Płuta adiutant sztabu przyjechał zawczora: Gotować się do marszu! Pójdziem, czy pod Turka, Czy na Francuza; oj, ten Bonapart figurka! Bez Suwarowa to on może nas wytuza. U nas w pułku gadano, jak szli na Francuza, Że Bonapart czarował, no, tak i Suwarów Czarował; tak i były czary przeciw czarów. Raz w bitwie, gdzie podział się? szukać Bonaparta - A on zmienił się w lisa, tak Suwarów w charta; Tak Bonaparte znowu w kota się przerzuca, Dalej drzeć pazurami, a Suwarów w kuca. Obaczcież, co się stało w końcu z Bonapartą...\" Tu Ryków przerwał i jadł; wtem z potrawą czwartą Wszedł służący i raptem boczne drzwi otwarto. Weszła nowa osoba, przystojna i młoda; Jej zjawienie się nagłe, jej wzrost i uroda, Jej ubiór zwrócił oczy; wszyscy ją witali; Prócz Tadeusza, widać, że ją wszyscy znali. Kibić miała wysmukłą, kształtną, pierś powabną, Suknię materyjalną, różową, jedwabną, Gors wycięty, kołnierzyk z korónek, rękawki Krótkie, w ręku kręciła wachlarz dla zabawki (Bo nie było gorąca); wachlarz pozłocisty Powiewając rozlewał deszcz iskier rzęsisty. Głowa do włosów, włosy pozwijane w kręgi, W pukle i przeplatane różowemi wstęgi, Pośród nich brylant, niby zakryty od oczu, Świecił się jako gwiazda w komety warkoczu - Słowem, ubior galowy; szeptali niejedni, Że zbyt wykwintny na wieś i na dzień powszedni. Nóżek, choć suknia krótka, oko nie zobaczy, Bo biegła bardzo szybko, suwała się raczéj, Jako osobki, które na trzykrólskie święta Przesuwają w jasełkach ukryte chłopięta. Biegła i wszystkich lekkim witając ukłonem, Chciała usieść na miejscu sobie zostawionem. Trudno było; bo krzeseł dla gości nie stało: Na czterech ławach cztery ich rzędy siedziało, Trzeba było rzęd ruszyć lub ławę przeskoczyć; Zręcznie między dwie ławy umiała się wtłoczyć, A potem między rzędem siedzących i stołem Jak bilardowa kula toczyła się kołem. W biegu dotknęła blisko naszego młodziana; Uczepiwszy falbaną o czyjeś kolana, Pośliznęła się nieco i w tym roztargnieniu Na pana Tadeusza wsparła się ramieniu. Przeprosiwszy go grzecznie, na miejscu swym siadła Pomiędzy nim i stryjem, ale nic nie jadła, Tylko się wachlowała, to wachlarza trzonek Kręciła, to kołnierzyk z brabanckich koronek Poprawiała, to lekkiem dotknięciem się ręki Muskała włosów pukle i wstąg jasnych pęki. Ta przerwa rozmów trwała już minut ze cztery. Tymczasem w końcu stoła naprzód ciche szmery, A potem się zaczęły wpółgłośne rozmowy; Mężczyźni rozsądzali swe dzisiejsze łowy. Asesora z Rejentem wzmogła się uparta, Coraz głośniejsza kłótnia o kusego charta, Którego posiadaniem pan Rejent się szczycił I utrzymywał, że on zająca pochwycił; Asesor zaś dowodził na złość Rejentowi, Że ta chwała należy chartu Sokołowi. Pytano zdania innych; więc wszyscy dokoła Brali stronę Kusego, albo też Sokoła, Ci jak znawcy, ci znowu jak naoczne świadki. Sędzia na drugim końcu do nowej sąsiadki Rzekł półgłosem: \"Przepraszam, musieliśmy siadać, Niepodobna wieczerzy na później odkładać: Goście głodni, chodzili daleko na pole; Myśliłem, że dziś z nami nie będziesz przy stole.\" To rzekłszy, z Podkomorzym przy pełnym kielichu O politycznych sprawach rozmawiał po cichu. Gdy tak były zajęte stołu strony obie, Tadeusz przyglądał się nieznanej osobie: Przypomniał, że za pierwszem na miejsce wejrzeniem Odgadnął zaraz, czyjem miało być siedzeniem. Rumienił się, serce mu biło nadzwyczajnie; Więc rozwiązane widział swych domysłów tajnie! Więc było przeznaczono, by przy jego boku Usiadła owa piękność widziana w pomroku. Wprawdzie zdała się teraz wzrostem dorodniejsza, Bo ubrana, a ubiór powiększa i zmniejsza. I włos u tamtej widział krótki, jasnozłoty, A u tej krucze, długie zwijały się sploty? Kolor musiał pochodzić od słońca promieni, Któremi przy zachodzie wszystko się czerwieni. Twarzy wówczas nie dostrzegł, nazbyt rychło znikła, Ale myśl twarz nadobną odgadywać zwykła; Myślił, że pewnie miała czarniutkie oczęta, Białą twarz, usta kraśne jak wiśnie bliźnięta; U tej znalazł podobne oczy, usta, lica; W wieku może by była największa różnica: Ogrodniczka dziewczynką zdawała się małą, A pani ta niewiastą już w latach dojrzałą; Lecz młodzież o piękności metrykę nie pyta, Bo młodzieńcowi młodą jest każda kobiéta, Chłopcowi każda piękność zda się rówiennicą, A niewinnemu każda kochanka dziewicą. Tadeusz, chociaż liczył lat blisko dwadzieście I od dzieciństwa mieszkał w Wilnie, wielkim mieście, Miał za dozorcę księdza, który go pilnował I w dawnej surowości prawidłach wychował. Tadeusz zatem przywiozł w strony swe rodzinne Duszę czystą, myśl żywą i serce niewinne, Ale razem niemałą chętkę do swywoli. Z góry już robił projekt, że sobie pozwoli Używać na wsi długo wzbronionej swobody; Wiedział, że był przystojny, czuł się rześki, młody, A w spadku po rodzicach wziął czerstwość i zdrowie. Nazywał się Soplica; wszyscy Soplicowie Są, jak wiadomo, krzepcy, otyli i silni, Do żołnierki jedyni, w naukach mniej pilni. Tadeusz się od przodków swoich nie odrodził: Dobrze na koniu jeździł, pieszo dzielnie chodził, Tępy nie był, lecz mało w naukach postąpił, Choć stryj na wychowanie niczego nie skąpił. On wolał z flinty strzelać albo szablą robić; Wiedział, że go myślano do wojska sposobić, Że ojciec w testamencie wyrzekł taką wolę; Ustawicznie do bębna tęsknił, siedząc w szkole. Ale stryj nagle pierwsze zamiary odmienił, Kazał, aby przyjechał i aby się żenił, I objął gospodarstwo; przyrzekł na początek Dać małą wieś, a potem cały swój majątek. Te wszystkie Tadeusza cnoty i zalety Ściągnęły wzrok sąsiadki, uważnej kobiety. Zmierzyła jego postać kształtną i wysoką, Jego ramiona silne, jego pierś szeroką I w twarz spójrzała, z której wytryskał rumieniec, Ilekroć z jej oczyma spotkał się młodzieniec: Bo z pierwszej lękliwości całkiem już ochłonął I patrzył wzrokiem śmiałym, w którym ogień płonął. Również patrzyła ona i cztery źrenice Gorzały przeciw sobie jak roratne świéce. Pierwsza z nim po francusku zaczęła rozmowę; Wracał z miasta, ze szkoły: więc o książki nowe, O autorów pytała Tadeusza zdania I ze zdań wyciągała na nowo pytania; Cóż gdy potem zaczęła mówić o malarstwie, O muzyce, o tańcach, nawet o rzeźbiarstwie! Dowiodła, że zna równie pędzel, noty, druki; Aż osłupiał Tadeusz na tyle nauki, Lękał się, by nie został pośmiewiska celem, I jąkał się jak żaczek przed nauczycielem. Szczęściem, że nauczyciel ładny i niesrogi; Odgadnęła sąsiadka powód jego trwogi, Wszczęła rzecz o mniej trudnych i mądrych przedmiotach: O wiejskiego pożycia nudach i kłopotach, I jak bawić się trzeba, i jak czas podzielić, By życie uprzyjemnić i wieś rozweselić. Tadeusz odpowiadał śmielej, szła rzecz daléj, W pół godziny już byli z sobą poufali; Zaczęli nawet małe żarciki i sprzeczki. W końcu stawiła przed nim trzy z chleba gałeczki: Trzy osoby na wybor; wziął najbliższą sobie; Podkomorzanki na to zmarszczyły się obie, Sąsiadka zaśmiała się, lecz nie powiedziała, Kogo owa szczęśliwsza gałka oznaczała. Inaczej bawiono się w drugim końcu stoła, Bo tam, wzmogłszy się nagle, stronnicy Sokoła Na partyję Kusego bez litości wsiedli: Spór był wielki, już potraw ostatnich nie jedli. Stojąc i pijąc obie kłóciły się strony, A najstraszniej pan Rejent był zacietrzewiony: Jak raz zaczął, bez przerwy rzecz swoją tokował I gestami ją bardzo dobitnie malował. (Był dawniej adwokatem pan rejent Bolesta, Zwano go kaznodzieją, że zbyt lubił gesta). Teraz ręce przy boku miał, w tył wygiął łokcie, Spod ramion wytknął palce i długie paznokcie, Przedstawiając dwa smycze chartów tym obrazem. Właśnie rzecz kończył: \"Wyczha! puściliśmy razem Ja i Asesor, razem, jakoby dwa kórki Jednym palcem spuszczone u jednej dwórórki; Wyczha! poszli, a zając jak struna - smyk w pole, Psy tuż (to mówiąc, ręce ciągnął wzdłuż po stole I palcami ruch chartów przedziwnie udawał), Psy tuż, i hec! od lasu odsadzili kawał; Sokoł smyk naprzód! rączy pies, lecz zagorzalec, Wysadził się przed Kusym o tyle, o palec; Wiedziałem, że spudłuje; szarak, gracz nie lada, Czchał niby prosto w pole, za nim psów gromada; Gracz szarak! skoro poczuł wszystkie charty w kupie, Pstręk na prawo, koziołka, z nim w prawo psy głupie, A on znowu fajt w lewo, jak wytnie dwa susy, Psy za nim fajt na lewo, on w las, a mój Kusy Cap !!\" - tak krzycząc pan Rejent, na stół pochylony, Z palcami swemi zabiegł aż do drugiej strony I \"cap!\" - Tadeuszowi wrzasnął tuż nad uchem. Tadeusz i sąsiadka, tym głosu wybuchem Znienacka przestraszeni właśnie w pół rozmowy, Odstrychnęli od siebie mimowolnie głowy, Jako wierzchołki drzewa powiązane społem, Gdy je wicher rozerwie; i ręce pod stołem Blisko siebie leżące wstecz nagle uciekły, I dwie twarze w jeden się rumieniec oblekły. Tadeusz, by nie zdradzić swego roztargnienia: \"Prawda\" - rzekł - mój Rejencie, prawda, bez wątpienia, Kusy piękny chart z kształtu, jeśli równie chwytny...\" \"Chwytny? - krzyknął pan Rejent.- Mój pies faworytny Żeby nie miał być chwytny?\" Więc Tadeusz znowu Cieszył się, że tak piękny pies nie ma narowu, Żałował, że go tylko widział idąc z lasu I że przymiotów jego poznać nie miał czasu. Na to zadrżał Asesor, puścił z rąk kieliszek, Utopił w Tadeusza wzrok jak bazyliszek. Asesor mniej krzykliwy i mniej był ruchawy Od Rejenta, szczuplejszy i mały z postawy, Lecz straszny na reducie, balu i sejmiku, Bo powiadano o nim: ma żądło w języku. Tak dowcipne żarciki umiał komponować, Iżby je w kalendarzu można wydrukować: Wszystkie złośliwe, ostre. Dawniej człek dostatni, Schedę ojca swojego i majątek bratni, Wszystko strwonił, na wielkim figurując świecie; Teraz wszedł w służbę rządu, by znaczyć w powiecie. Lubił bardzo myślistwo, już to dla zabawy, Już to że odgłos trąbki i widok obławy Przypominał mu jego lata młodociane, Kiedy miał strzelców licznych i psy zawołane; Teraz mu z całej psiarni dwa charty zostały, I jeszcze z tych jednemu chciano przeczyć chwały. Więc zbliżył się i, z wolna gładząc faworyty, Rzekł z uśmiechem, a był to uśmiech jadowity: \"Chart bez ogona jest jak szlachcic bez urzędu... Ogon też znacznie chartom pomaga do pędu, A Pan kusość uważasz za dowód dobroci? Zresztą zdać się możemy na sąd Pańskiej cioci. Choć pani Telimena mieszkała w stolicy I bawi się niedawno w naszej okolicy, Lepiej zna się na łowach niż myśliwi młodzi: Tak to nauka sama z latami przychodzi\". Tadeusz, na którego niespodzianie spadał Grom taki, wstał zmieszany, chwilę nic nie gadał, Lecz patrzył na rywala coraz straszniéj, srożéj... Wtem, wielkim szczęściem, dwakroć kichnął Podkomorzy. \"Wiwat!\" - krzyknęli wszyscy; on się wszystkim skłonił I z wolna w tabakierę palcami zadzwonił: Tabakiera ze złota, z brylantów oprawa, A w środku jej był portret króla Stanisława. Ojcu Podkomorzego sam król ją darował, Po ojcu Podkomorzy godnie ją piastował; Gdy w nię dzwonił, znak dawał, że miał głos zabierać; Umilkli wszyscy i ust nie śmieli otwierać. On rzekł: \"Wielmożni Szlachta, Bracia Dobrodzieje! Forum myśliwskiem tylko są łąki i knieje, Więc ja w domu podobnych spraw nie decyduję I posiedzenie nasze na jutro solwuję, I dalszych replik stronom dzisiaj nie dozwolę. Woźny! odwołaj sprawę na jutro na pole. Jutro i Hrabia z całym myślistwem tu zjedzie, I Waszeć z nami ruszysz, Sędzio, mój sąsiedzie, I pani Telimena, i panny, i panie, Słowem, zrobim na urząd wielkie polowanie; I Wojski towarzystwa nam też nie odmówi\". To mówiąc tabakierę podawał starcowi. Wojski na ostrym końcu śród myśliwych siedział, Słuchał, zmrużywszy oczy, słowa nie powiedział, Choć młodzież nieraz jego zasięgała zdania, Bo nikt lepiej nad niego nie znał polowania. On milczał, szczyptę wziętą z tabakiery ważył W palcach i długo dumał, nim ją w końcu zażył; Kichnął, aż cała izba rozległa się echem, I potrząsając głową rzekł z gorzkim uśmiechem: \"O, jak mnie to starego i smuci, i dziwi! Cóż by to o tem starzy mówili myśliwi, Widząc, że w tylu szlachty, w tylu panów gronie Mają sądzić się spory o charcim ogonie; Cóż by rzekł na to stary Rejtan, gdyby ożył? Wróciłby do Lachowicz i w grób się położył! Co by rzekł wojewoda Niesiołowski stary, Który ma dotąd pierwsze na świecie ogary I dwiestu strzelców trzyma obyczajem pańskim, I ma sto wozów sieci w zamku worończańskim, A od tylu lat siedzi jak mnich na swym dworze. Nikt go na polowanie uprosić nie może, Białopiotrowiczowi samemu odmówił! Bo cóż by on na waszych polowaniach łowił? Piękna byłaby sława, ażeby pan taki Wedle dzisiejszej mody jeździł na szaraki! Za moich, panie, czasów w języku strzeleckim Dzik, niedźwiedź, łoś, wilk zwany był zwierzem szlacheckim, A zwierzę nie mające kłów, rogów, pazurów Zostawiano dla płatnych sług i dworskich ciurów; Żaden pan nigdy przyjąć nie chciałby do ręki Strzelby, którą zhańbiono, sypiąc w nią śrót cienki! Trzymano wprawdzie chartów, bo z łowów wracając, Trafia się, że spod konia mknie się biedak zając; Puszczano wtenczas za nim dla zabawki smycze I na konikach małe goniły panicze Przed oczami rodziców, którzy te pogonie Ledwie raczyli widzieć, cóż kłócić się o nie! Więc niech Jaśnie Wielmożny Podkomorzy raczy Odwołać swe rozkazy i niech mi wybaczy, Że nie mogę na takie jechać polowanie I nigdy na niem noga moja nie postanie! Nazywam się Hreczecha, a od króla Lecha Żaden za zającami nie jeździł Hreczecha\". Tu śmiech młodzieży mowę Wojskiego zagłuszył. Wstano od stołu; pierwszy Podkomorzy ruszył; Z wieku mu i z urzędu ten zaszczyt należy; Idąc kłaniał się damom, starcom i młodzieży; Za nim szedł kwestarz, Sędzia tuż przy Bernardynie, Sędzia u progu rękę dał Podkomorzynie, Tadeusz Telimenie, Asesor Krajczance, A pan Rejent na końcu Wojskiej Hreczeszance. Tadeusz z kilku gośćmi poszedł do stodoły, A czuł się pomięszany, zły i niewesoły, Rozbierał myślą wszystkie dzisiejsze wypadki: Spotkanie się, wieczerzę przy boku sąsiadki, A szczególniej mu słowo \"ciocia\" koło ucha Brzęczało ciągle jako naprzykrzona mucha. Pragnąłby u Woźnego lepiej się wypytać O pani Telimenie, lecz go nie mógł schwytać; Wojskiego też nie widział, bo zaraz z wieczerzy Wszyscy poszli za gośćmi, jak sługom należy, Urządzając we dworze izby do spoczynku. Starsi i damy spały we dworskim budynku, Młodzież Tadeuszowi prowadzić kazano, W zastępstwie gospodarza, w stodołę na siano. W pół godziny tak było głucho w całym dworze Jako po zadzwonieniu na pacierz w klasztorze; Ciszę przerywał tylko głos nocnego stróża. Usnęli wszyscy. Sędzia sam oczu nie zmruża: Jako wódz gospodarstwa obmyśla wyprawę W pole i w domu przyszłą urządza zabawę. Dał rozkaz ekonomom, wójtom i gumiennym, Pisarzom, ochmistrzyni, strzelcom i stajennym, I musiał wszystkie dzienne rachunki przezierać, Nareszcie rzekł Woźnemu, że się chce rozbierać. Woźny pas mu odwiązał, pas słucki, pas lity, Przy którym świecą gęste kutasy jak kity, Z jednej strony złotogłów w purpurowe kwiaty, Na wywrót jedwab czarny, posrebrzany w kraty; Pas taki można równie kłaść na strony obie: Złotą na dzień galowy, a czarną w żałobie. Sam Woźny umiał pas ten odwiązywać, składać; Właśnie tem się zatrudniał i kończył tak gadać: \"Cóż złego, że przeniosłem stoły do zamczyska? Nikt na tem nic nie stracił, a Pan może zyska, Bo przecież o ten zamek dziś toczy się sprawa. My od dzisiaj do zamku nabyliśmy prawa, I mimo całą strony przeciwnej zajadłość Dowiodę, że zamczysko wzięliśmy w posiadłość. Wszakże kto gości prosi w zamek na wieczerzę, Dowodzi, że posiadłość tam ma albo bierze; Nawet strony przeciwne weźwiemy na świadki: Pamiętam za mych czasów podobne wypadki\". Już Sędzia spał. Więc Woźny cicho wszedł do sieni, Siadł przy świecy i dobył książeczkę z kieszeni, Która mu jak Ołtarzyk złoty zawsze służy, Której nigdy nie rzuca w domu i w podróży. Była to trybunalska wokanda: tam rzędem Stały spisane sprawy, które przed urzędem Woźny sam głosem swoim przed laty wywołał Albo o których później dowiedzieć się zdołał. Prostym ludziom wokanda zda się imion spisem, Woźnemu jest obrazów wspaniałych zarysem. Czytał więc i rozmyślał: Ogiński z Wizgirdem, Dominikanie z Rymszą, Rymsza z Wysogirdem, Radziwiłł z Wereszczaką, Giedrojć z Rodułtowskim, Obuchowicz z kahałem, Juraha z Piotrowskim, Maleski z Mickiewiczem, a na koniec Hrabia Z Soplicą: i czytając, z tych imion wywabia Pamięć spraw wielkich, wszystkie procesu wypadki, I stają mu przed oczy sąd, strony i świadki; I ogląda sam siebie, jak w żupanie białym, W granatowym kontuszu stał przed trybunałem; Jedna ręka na szabli, a drugą do stoła Przywoławszy dwie strony: \"Uciszcie się!\" - woła. Marząc i kończąc pacierz wieczorny, pomału Usnął ostatni w Litwie Woźny trybunału. Takie były zabawy, spory w one lata Śród cichej wsi litewskiej, kiedy reszta świata We łzach i krwi tonęła, gdy ów mąż, bóg wojny, Otoczon chmurą pułków, tysiącem dział zbrojny, Wprzągłszy w swój rydwan orły złote obok srebrnych, Od puszcz libijskich latał do Alpów podniebnych, Ciskając grom po gromie: w Piramidy, w Tabor, W Marengo, w Ulm, w Austerlitz. Zwycięstwo i Zabor Biegły przed nim i za nim. Sława czynów tylu, Brzemienna imionami rycerzy, od Nilu Szła hucząc ku północy, aż u Niemna brzegów Odbiła się, jak od skał, od Moskwy szeregów, Które broniły Litwę murami żelaza Przed wieścią, dla Rosyi straszną jak zaraza. Przecież nieraz nowina, niby kamień z nieba, Spadała w Litwę; nieraz dziad żebrzący chleba, Bez ręki lub bez nogi, przyjąwszy jałmużnę, Stanął i oczy wkoło obracał ostróżne. Gdy nie widział we dworze rosyjskich żołnierzy Ani jarmułek, ani czerwonych kołnierzy, Wtenczas, kim był, wyznawał: był legijonistą, Przynosił kości stare na ziemię ojczystą, Której już bronić nie mógł... Jak go wtenczas cała Rodzina pańska, jak go czeladka ściskała, Zanosząc się od płaczu! On za stołem siadał I dziwniejsze od baśni historyje gadał. On opowiadał, jako jenerał Dąbrowski Z ziemi włoskiej stara się przyciągnąć do Polski, Jak on rodaków zbiera na lombardzkim polu; Jak Kniaziewicz rozkazy daje z Kapitolu I,zwycięzca, wydartych potomkom Cezarów Rzucił w oczy Francuzów sto krwawych sztandarów; Jak Jabłonowski zabiegł, aż kędy pieprz rośnie, Gdzie się cukier wytapia i gdzie w wiecznej wiośnie Pachnące kwitną lasy; z legiją Dunaju Tam wódz Murzyny gromi, a wzdycha do kraju. Mowy starca krążyły we wsi po kryjomu; Chłopiec, co je posłyszał, znikał nagle z domu, Lasami i bagnami skradał się tajemnie, Ścigany od Moskali, skakał kryć się w Niemnie I nurkiem płynął na brzeg Księstwa Warszawskiego, Gdzie usłyszał głos miły: \"Witaj nam, kolego!\" Lecz nim odszedł, wyskoczył na wzgórek z kamienia I Moskalom przez Niemen rzekł: \"Do zobaczenia!\" Tak przekradł się Gorecki, Pac i Obuchowicz, Piotrowski, Obolewski, Rożycki, Janowicz, Mierzejewscy, Brochocki i Bernatowicze, Kupść, Gedymin i inni, których nie policzę; Opuszczali rodziców i ziemię kochaną, I dobra, które na skarb carski zabierano. Czasem do Litwy kwestarz z obcego klasztoru Przyszedł i kiedy bliżej poznał panów dworu, Gazetę im pokazał wyprutą z szkaplerza; Tam stała wypisana i liczba żołnierza, I nazwisko każdego wodza legijonu, I każdego z nich opis zwycięstwa lub zgonu. Po wielu latach pierwszy raz miała rodzina Wieść o życiu, o chwale i o śmierci syna; Brał dom żałobę, ale powiedzieć nie śmiano, Po kim była żałoba, tylko zgadywano W okolicy; i tylko cichy smutek panów Lub cicha radość była gazetą ziemianów. Takim kwestarzem tajnym był Robak podobno: Często on z panem Sędzią rozmawiał osobno; Po tych rozmowach zawsze jakowaś nowina Rozeszła się w sąsiedztwie. Postać Bernardyna Wydawała, że mnich ten nie zawsze w kapturze Chodził i nie w klasztornym zestarzał się murze. Miał on nad prawym uchem, nieco wyżej skroni, Bliznę wyciętej skóry na szerokość dłoni I w brodzie ślad niedawny lancy lub postrzału, Ran tych nie dostał pewnie przy czytaniu mszału. Ale nie tylko groźne wejrzenie i blizny, Lecz sam ruch i głos jego miał coś żołnierszczyzny. Przy mszy, gdy z wzniesionymi zwracał się rękami Od ołtarza do ludu, by mówić:,,Pan z wami\", To nieraz tak się zręcznie skręcił jednym razem, Jakby ,,prawo w tył\" robił za wodza rozkazem, I słowa liturgiji takim wyrzekł tonem Do ludu, jak oficer stojąc przed szwadronem; Postrzegali to chłopcy służący mu do mszy. Spraw także politycznych był Robak świadomszy Niźli żywotów świętych, a jeżdżąc po kweście, Często zastanawiał się w powiatowym mieście; Miał pełno interesów: to listy odbierał, Których nigdy przy obcych ludziach nie otwierał, To wysyłał posłańców, ale gdzie i po co, Nie powiadał; częstokroć wymykał się nocą Do dworów pańskich, z szlachtą ustawicznie szeptał I okoliczne wioski dokoła wydeptał, I w karczmach z wieśniakami rozprawiał niemało, A zawsze o tem, co się w cudzych krajach działo. Teraz Sędziego, który już spał od godziny, Przychodzi budzić; pewnie ma jakieś nowiny.We’re no strangers to love You know the rules and so do I (do I) A full commitment’s what I’m thinking of You wouldn’t get this from any other guy I just wanna tell you how I’m feeling Gotta make you understand Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you We’ve known each other for so long Your heart’s been aching, but you’re too shy to say it (say it) Inside, we both know what’s been going on (going on) We know the game and we’re gonna play it And if you ask me how I’m feeling Don’t tell me you’re too blind to see Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you (Ooh, give you up) (Ooh, give you up) (Ooh) Never gonna give, never gonna give (give you up) (Ooh) Never gonna give, never gonna give (give you up) We’ve known each other for so long Your heart’s been aching, but you’re too shy to say it (to say it) Inside, we both know what’s been going on (going on) We know the game and we’re gonna play it I just wanna tell you how I’m feeling Gotta make you understand Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you Never gonna give you up Never gonna let you down Never gonna run around and desert you Never gonna make you cry Never gonna say goodbye Never gonna tell a lie and hurt you “Well, Prince, so Genoa and Lucca are now just family estates of the Buonapartes. But I warn you, if you don’t tell me that this means war, if you still try to defend the infamies and horrors perpetrated by that Antichrist—I really believe he is Antichrist—I will have nothing more to do with you and you are no longer my friend, no longer my ‘faithful slave,’ as you call yourself! But how do you do? I see I have frightened you—sit down and tell me all the news.” It was in July, 1805, and the speaker was the well-known Anna Pávlovna Schérer, maid of honor and favorite of the Empress Márya Fëdorovna. With these words she greeted Prince Vasíli Kurágin, a man of high rank and importance, who was the first to arrive at her reception. Anna Pávlovna had had a cough for some days. She was, as she said, suffering from la grippe; grippe being then a new word in St. Petersburg, used only by the elite. All her invitations without exception, written in French, and delivered by a scarlet-liveried footman that morning, ran as follows: “If you have nothing better to do, Count (or Prince), and if the prospect of spending an evening with a poor invalid is not too terrible, I shall be very charmed to see you tonight between 7 and 10—Annette Schérer.” “Heavens! what a virulent attack!” replied the prince, not in the least disconcerted by this reception. He had just entered, wearing an embroidered court uniform, knee breeches, and shoes, and had stars on his breast and a serene expression on his flat face. He spoke in that refined French in which our grandfathers not only spoke but thought, and with the gentle, patronizing intonation natural to a man of importance who had grown old in society and at court. He went up to Anna Pávlovna, kissed her hand, presenting to her his bald, scented, and shining head, and complacently seated himself on the sofa. “First of all, dear friend, tell me how you are. Set your friend’s mind at rest,” said he without altering his tone, beneath the politeness and affected sympathy of which indifference and even irony could be discerned. “Can one be well while suffering morally? Can one be calm in times like these if one has any feeling?” said Anna Pávlovna. “You are staying the whole evening, I hope?” “And the fete at the English ambassador’s? Today is Wednesday. I must put in an appearance there,” said the prince. “My daughter is coming for me to take me there.” “I thought today’s fete had been canceled. I confess all these festivities and fireworks are becoming wearisome.” “If they had known that you wished it, the entertainment would have been put off,” said the prince, who, like a wound-up clock, by force of habit said things he did not even wish to be believed. “Don’t tease! Well, and what has been decided about Novosíltsev’s dispatch? You know everything.” “What can one say about it?” replied the prince in a cold, listless tone. “What has been decided? They have decided that Buonaparte has burnt his boats, and I believe that we are ready to burn ours.” Prince Vasíli always spoke languidly, like an actor repeating a stale part. Anna Pávlovna Schérer on the contrary, despite her forty years, overflowed with animation and impulsiveness. To be an enthusiast had become her social vocation and, sometimes even when she did not feel like it, she became enthusiastic in order not to disappoint the expectations of those who knew her. The subdued smile which, though it did not suit her faded features, always played round her lips expressed, as in a spoiled child, a continual consciousness of her charming defect, which she neither wished, nor could, nor considered it necessary, to correct. In the midst of a conversation on political matters Anna Pávlovna burst out: “Oh, don’t speak to me of Austria. Perhaps I don’t understand things, but Austria never has wished, and does not wish, for war. She is betraying us! Russia alone must save Europe. Our gracious sovereign recognizes his high vocation and will be true to it. That is the one thing I have faith in! Our good and wonderful sovereign has to perform the noblest role on earth, and he is so virtuous and noble that God will not forsake him. He will fulfill his vocation and crush the hydra of revolution, which has become more terrible than ever in the person of this murderer and villain! We alone must avenge the blood of the just one.... Whom, I ask you, can we rely on?... England with her commercial spirit will not and cannot understand the Emperor Alexander’s loftiness of soul. She has refused to evacuate Malta. She wanted to find, and still seeks, some secret motive in our actions. What answer did Novosíltsev get? None. The English have not understood and cannot understand the self-abnegation of our Emperor who wants nothing for himself, but only desires the good of mankind. And what have they promised? Nothing! And what little they have promised they will not perform! Prussia has always declared that Buonaparte is invincible, and that all Europe is powerless before him.... And I don’t believe a word that Hardenburg says, or Haugwitz either. This famous Prussian neutrality is just a trap. I have faith only in God and the lofty destiny of our adored monarch. He will save Europe!” She suddenly paused, smiling at her own impetuosity. “I think,” said the prince with a smile, “that if you had been sent instead of our dear Wintzingerode you would have captured the King of Prussia’s consent by assault. You are so eloquent. Will you give me a cup of tea?” “In a moment. À propos,” she added, becoming calm again, “I am expecting two very interesting men tonight, le Vicomte de Mortemart, who is connected with the Montmorencys through the Rohans, one of the best French families. He is one of the genuine émigrés, the good ones. And also the Abbé Morio. Do you know that profound thinker? He has been received by the Emperor. Had you heard?” “I shall be delighted to meet them,” said the prince. “But tell me,” he added with studied carelessness as if it had only just occurred to him, though the question he was about to ask was the chief motive of his visit, “is it true that the Dowager Empress wants Baron Funke to be appointed first secretary at Vienna? The baron by all accounts is a poor creature.” Prince Vasíli wished to obtain this post for his son, but others were trying through the Dowager Empress Márya Fëdorovna to secure it for the baron. Anna Pávlovna almost closed her eyes to indicate that neither she nor anyone else had a right to criticize what the Empress desired or was pleased with. “Baron Funke has been recommended to the Dowager Empress by her sister,” was all she said, in a dry and mournful tone. As she named the Empress, Anna Pávlovna’s face suddenly assumed an expression of profound and sincere devotion and respect mingled with sadness, and this occurred every time she mentioned her illustrious patroness. She added that Her Majesty had deigned to show Baron Funke beaucoup d’estime, and again her face clouded over with sadness. The prince was silent and looked indifferent. But, with the womanly and courtierlike quickness and tact habitual to her, Anna Pávlovna wished both to rebuke him (for daring to speak as he had done of a man recommended to the Empress) and at the same time to console him, so she said: “Now about your family. Do you know that since your daughter came out everyone has been enraptured by her? They say she is amazingly beautiful.” The prince bowed to signify his respect and gratitude. “I often think,” she continued after a short pause, drawing nearer to the prince and smiling amiably at him as if to show that political and social topics were ended and the time had come for intimate conversation—“I often think how unfairly sometimes the joys of life are distributed. Why has fate given you two such splendid children? I don’t speak of Anatole, your youngest. I don’t like him,” she added in a tone admitting of no rejoinder and raising her eyebrows. “Two such charming children. And really you appreciate them less than anyone, and so you don’t deserve to have them.” And she smiled her ecstatic smile. “I can’t help it,” said the prince. “Lavater would have said I lack the bump of paternity.” “Don’t joke; I mean to have a serious talk with you. Do you know I am dissatisfied with your younger son? Between ourselves” (and her face assumed its melancholy expression), “he was mentioned at Her Majesty’s and you were pitied....” The prince answered nothing, but she looked at him significantly, awaiting a reply. He frowned. “What would you have me do?” he said at last. “You know I did all a father could for their education, and they have both turned out fools. Hippolyte is at least a quiet fool, but Anatole is an active one. That is the only difference between them.” He said this smiling in a way more natural and animated than usual, so that the wrinkles round his mouth very clearly revealed something unexpectedly coarse and unpleasant. “And why are children born to such men as you? If you were not a father there would be nothing I could reproach you with,” said Anna Pávlovna, looking up pensively. “I am your faithful slave and to you alone I can confess that my children are the bane of my life. It is the cross I have to bear. That is how I explain it to myself. It can’t be helped!” He said no more, but expressed his resignation to cruel fate by a gesture. Anna Pávlovna meditated. “Have you never thought of marrying your prodigal son Anatole?” she asked. “They say old maids have a mania for matchmaking, and though I don’t feel that weakness in myself as yet, I know a little person who is very unhappy with her father. She is a relation of yours, Princess Mary Bolkónskaya.” Prince Vasíli did not reply, though, with the quickness of memory and perception befitting a man of the world, he indicated by a movement of the head that he was considering this information. “Do you know,” he said at last, evidently unable to check the sad current of his thoughts, “that Anatole is costing me forty thousand rubles a year? And,” he went on after a pause, “what will it be in five years, if he goes on like this?” Presently he added: “That’s what we fathers have to put up with.... Is this princess of yours rich?” “Her father is very rich and stingy. He lives in the country. He is the well-known Prince Bolkónski who had to retire from the army under the late Emperor, and was nicknamed ‘the King of Prussia.’ He is very clever but eccentric, and a bore. The poor girl is very unhappy. She has a brother; I think you know him, he married Lise Meinen lately. He is an aide-de-camp of Kutúzov’s and will be here tonight.” “Listen, dear Annette,” said the prince, suddenly taking Anna Pávlovna’s hand and for some reason drawing it downwards. “Arrange that affair for me and I shall always be your most devoted slave-slafe with an f, as a village elder of mine writes in his reports. She is rich and of good family and that’s all I want.” And with the familiarity and easy grace peculiar to him, he raised the maid of honor’s hand to his lips, kissed it, and swung it to and fro as he lay back in his armchair, looking in another direction. “Attendez,” said Anna Pávlovna, reflecting, “I’ll speak to Lise, young Bolkónski’s wife, this very evening, and perhaps the thing can be arranged. It shall be on your family’s behalf that I’ll start my apprenticeship as old maid.” CHAPTER II Anna Pávlovna’s drawing room was gradually filling. The highest Petersburg society was assembled there: people differing widely in age and character but alike in the social circle to which they belonged. Prince Vasíli’s daughter, the beautiful Hélène, came to take her father to the ambassador’s entertainment; she wore a ball dress and her badge as maid of honor. The youthful little Princess Bolkónskaya, known as la femme la plus séduisante de Pétersbourg, * was also there. She had been married during the previous winter, and being pregnant did not go to any large gatherings, but only to small receptions. Prince Vasíli’s son, Hippolyte, had come with Mortemart, whom he introduced. The Abbé Morio and many others had also come. To each new arrival Anna Pávlovna said, “You have not yet seen my aunt,” or “You do not know my aunt?” and very gravely conducted him or her to a little old lady, wearing large bows of ribbon in her cap, who had come sailing in from another room as soon as the guests began to arrive; and slowly turning her eyes from the visitor to her aunt, Anna Pávlovna mentioned each one’s name and then left them. Each visitor performed the ceremony of greeting this old aunt whom not one of them knew, not one of them wanted to know, and not one of them cared about; Anna Pávlovna observed these greetings with mournful and solemn interest and silent approval. The aunt spoke to each of them in the same words, about their health and her own, and the health of Her Majesty, “who, thank God, was better today.” And each visitor, though politeness prevented his showing impatience, left the old woman with a sense of relief at having performed a vexatious duty and did not return to her the whole evening. The young Princess Bolkónskaya had brought some work in a gold-embroidered velvet bag. Her pretty little upper lip, on which a delicate dark down was just perceptible, was too short for her teeth, but it lifted all the more sweetly, and was especially charming when she occasionally drew it down to meet the lower lip. As is always the case with a thoroughly attractive woman, her defect—the shortness of her upper lip and her half-open mouth—seemed to be her own special and peculiar form of beauty. Everyone brightened at the sight of this pretty young woman, so soon to become a mother, so full of life and health, and carrying her burden so lightly. Old men and dull dispirited young ones who looked at her, after being in her company and talking to her a little while, felt as if they too were becoming, like her, full of life and health. All who talked to her, and at each word saw her bright smile and the constant gleam of her white teeth, thought that they were in a specially amiable mood that day. The little princess went round the table with quick, short, swaying steps, her workbag on her arm, and gaily spreading out her dress sat down on a sofa near the silver samovar, as if all she was doing was a pleasure to herself and to all around her. “I have brought my work,” said she in French, displaying her bag and addressing all present. “Mind, Annette, I hope you have not played a wicked trick on me,” she added, turning to her hostess. “You wrote that it was to be quite a small reception, and just see how badly I am dressed.” And she spread out her arms to show her short-waisted, lace-trimmed, dainty gray dress, girdled with a broad ribbon just below the breast.']" + +# Short descritpion of the query semantics. +description = "select the long key" + +[queries.results] +# Number of expected matches. +count = 1 +# Byte locations of spans of all matches, in order. +spans = [[69298, 69300]] +# Stringified values of all matches, verbatim as in the input, +# in the same order as above. +nodes = [ + '42', +] \ No newline at end of file diff --git a/crates/rsonpath/src/error.rs b/crates/rsonpath/src/error.rs index 3959e4f2..07d03397 100644 --- a/crates/rsonpath/src/error.rs +++ b/crates/rsonpath/src/error.rs @@ -76,7 +76,6 @@ fn report_query_syntax_error(query_string: &str, report: ParseErrorReport) -> ey let error_slice = &query_string[error.start_idx..error.start_idx + error.len]; let slice = &query_string[display_start_idx..display_start_idx + display_length]; let error_idx = error.start_idx - display_start_idx; - let underline: String = iter::repeat(' ') .take(error_idx) .chain(iter::repeat('^').take(error.len)) diff --git a/crates/rsonpath/src/runner.rs b/crates/rsonpath/src/runner.rs index 25751671..26b04bd1 100644 --- a/crates/rsonpath/src/runner.rs +++ b/crates/rsonpath/src/runner.rs @@ -7,7 +7,7 @@ use eyre::{Result, WrapErr}; use log::warn; use rsonpath_lib::{ engine::{error::EngineError, main::MainEngine, Compiler, Engine}, - input::{BufferedInput, Input, MmapInput, OwnedBytes}, + input::{BorrowedBytes, BufferedInput, Input, MmapInput, OwnedBytes}, query::automaton::Automaton, result::MatchWriter, }; @@ -116,21 +116,22 @@ impl> ResolvedInput { }, } } - ResolvedInputKind::Owned => { - let input = match self.file { - JsonSource::File(f) => { - let contents = get_contents(f)?; - OwnedBytes::new(&contents) - } - JsonSource::Stdin(s) => { - let contents = get_contents(s)?; - OwnedBytes::new(&contents) - } - JsonSource::Inline(j) => OwnedBytes::new(&j.as_ref()), - }?; - - with_output.run_and_output(engine, input) - } + ResolvedInputKind::Owned => match self.file { + JsonSource::File(f) => { + let contents = get_contents(f)?; + let input = OwnedBytes::new(contents.into_bytes()); + with_output.run_and_output(engine, input) + } + JsonSource::Stdin(s) => { + let contents = get_contents(s)?; + let input = OwnedBytes::new(contents.into_bytes()); + with_output.run_and_output(engine, input) + } + JsonSource::Inline(j) => { + let input = BorrowedBytes::new(j.as_ref().as_bytes()); + with_output.run_and_output(engine, input) + } + }, ResolvedInputKind::Buffered => { let read = self .file diff --git a/fuzz/fuzz_targets/fuzz_arbitrary_bytes.rs b/fuzz/fuzz_targets/fuzz_arbitrary_bytes.rs index d1d36005..4dc19ce2 100644 --- a/fuzz/fuzz_targets/fuzz_arbitrary_bytes.rs +++ b/fuzz/fuzz_targets/fuzz_arbitrary_bytes.rs @@ -2,12 +2,12 @@ use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target}; use rsonpath::engine::{Compiler, Engine, RsonpathEngine}; -use rsonpath::input::OwnedBytes; +use rsonpath::input::BorrowedBytes; use rsonpath::query::JsonPathQuery; use std::fmt::Debug; fuzz_target!(|data: DisplayableBytes| { - let bytes = OwnedBytes::new(&data.0).expect("error creating input"); + let bytes = BorrowedBytes::new(data.0); let query = JsonPathQuery::parse("$..*").expect("error when parsing the query"); let engine = RsonpathEngine::compile_query(&query).expect("error when compiling"); let mut sink = vec![]; diff --git a/fuzz/fuzz_targets/fuzz_arbitrary_json.rs b/fuzz/fuzz_targets/fuzz_arbitrary_json.rs index 906ea816..2fcd4b97 100644 --- a/fuzz/fuzz_targets/fuzz_arbitrary_json.rs +++ b/fuzz/fuzz_targets/fuzz_arbitrary_json.rs @@ -1,7 +1,7 @@ #![no_main] use libfuzzer_sys::{arbitrary::Arbitrary, fuzz_target, Corpus}; -use rsonpath::input::OwnedBytes; +use rsonpath::input::BorrowedBytes; use rsonpath::query::JsonPathQuery; use rsonpath::{ engine::{Compiler, Engine, RsonpathEngine}, @@ -20,7 +20,7 @@ struct FuzzData { fuzz_target!(|data: FuzzData| -> Corpus { let json_string = data.json.to_string(); - let bytes = OwnedBytes::new(&json_string).expect("error creating input"); + let bytes = BorrowedBytes::new(json_string.as_bytes()); let engine = match RsonpathEngine::compile_query(&data.query) { Ok(x) => x, Err(CompilerError::QueryTooComplex(_)) => return Corpus::Reject, diff --git a/rsonpath.code-workspace b/rsonpath.code-workspace index ea3a0f64..2db9d0e5 100644 --- a/rsonpath.code-workspace +++ b/rsonpath.code-workspace @@ -60,6 +60,7 @@ "rustdoc", "RUSTFLAGS", "rustfmt", + "Seekable", "SIMD", "snaks", "srli", @@ -91,6 +92,6 @@ "./crates/rsonpath-test/Cargo.toml", "./crates/rsonpath-test-codegen/Cargo.toml", "./fuzz/Cargo.toml" - ] + ], } } \ No newline at end of file