unicode-intervals 0.3.1

Search for Unicode code points intervals by including/excluding categories, ranges, and custom characters sets.
Documentation
use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
use unicode_intervals::{internals, UnicodeCategory, UnicodeCategorySet, UnicodeVersion};

fn version(c: &mut Criterion) {
    let version = black_box(UnicodeVersion::V15_0_0);
    c.bench_function("version - normalized_categories", |b| {
        b.iter(|| version.normalized_categories())
    });
}

fn intervals(c: &mut Criterion) {
    let string = black_box("abcdef0123456789");
    let long_string =
        black_box("zxcvbnm,./asdfghjkl;'qwertyuiopZXCVBNM<>?ASDFGHJKL:QWERTYUIOP{}0123456");
    c.bench_function("intervals - from_str short", |b| {
        b.iter(|| internals::intervals::from_str(string))
    });
    c.bench_function("intervals - from_str long", |b| {
        b.iter(|| internals::intervals::from_str(long_string))
    });
    let uppercase = UnicodeVersion::V15_0_0.intervals_for(UnicodeCategory::Lu);
    let lowercase = UnicodeVersion::V15_0_0.intervals_for(UnicodeCategory::Ll);
    c.bench_function("intervals - subtract", |b| {
        b.iter(|| internals::intervals::subtract(lowercase.to_vec(), uppercase))
    });
    // Realistic `query` shape: a large category minus a few scattered codepoints
    // (e.g. `exclude_characters`), so most of `left` is copied in bulk runs.
    let large = UnicodeVersion::V15_0_0.intervals_for(UnicodeCategory::Lo);
    let few = internals::intervals::from_str("\u{0E01}\u{4E2D}\u{A000}");
    c.bench_function("intervals - subtract sparse", |b| {
        b.iter(|| internals::intervals::subtract(large.to_vec(), &few))
    });
    // Concatenated, sorted-per-category slices that interleave across the codepoint space:
    // the input shape `query` feeds to `merge` for a multi-category set.
    let multi = internals::query::intervals_for_set(
        UnicodeVersion::V15_0_0,
        UnicodeCategory::Lu
            | UnicodeCategory::Ll
            | UnicodeCategory::Lo
            | UnicodeCategory::Nd
            | UnicodeCategory::Po
            | UnicodeCategory::Sm,
    )
    .into_owned();
    c.bench_function("intervals - merge multi-category", |b| {
        b.iter_batched(
            || multi.clone(),
            |mut intervals| internals::intervals::merge(&mut intervals),
            BatchSize::SmallInput,
        )
    });
}

fn categories(c: &mut Criterion) {
    let all_categories = black_box(UnicodeCategorySet::all());
    c.bench_function("categories - set - display - all", |b| {
        b.iter(|| all_categories.to_string())
    });
    let few_categories = UnicodeCategory::Lm | UnicodeCategory::Sk | UnicodeCategory::Zl;
    c.bench_function("categories - set - display - few", |b| {
        b.iter(|| few_categories.to_string())
    });
    c.bench_function("categories - merge", |b| {
        b.iter(|| {
            internals::categories::merge(
                Some(all_categories),
                black_box(UnicodeCategory::Lu.into()),
            )
        })
    });
}

fn query(c: &mut Criterion) {
    let version = black_box(UnicodeVersion::V15_0_0);
    c.bench_function("query - intervals_for_set - empty", |b| {
        b.iter(|| {
            internals::query::intervals_for_set(version, black_box(UnicodeCategorySet::new()))
        })
    });
    c.bench_function("query - intervals_for_set - all", |b| {
        b.iter(|| {
            internals::query::intervals_for_set(version, black_box(UnicodeCategorySet::all()))
        })
    });
    c.bench_function("query - intervals_for_set - single large", |b| {
        b.iter(|| {
            internals::query::intervals_for_set(version, black_box(UnicodeCategory::Lu).into())
        })
    });
    c.bench_function("query - intervals_for_set - multiple", |b| {
        b.iter(|| {
            internals::query::intervals_for_set(
                version,
                black_box(UnicodeCategory::Lu | UnicodeCategory::M),
            )
        })
    });
    // Full range + multiple categories: hits the concat + sort/merge path over the whole set.
    c.bench_function("query - multiple categories - full range", |b| {
        b.iter(|| {
            version
                .query()
                .include_categories(black_box(
                    UnicodeCategory::Lu
                        | UnicodeCategory::Ll
                        | UnicodeCategory::Lo
                        | UnicodeCategory::Nd
                        | UnicodeCategory::Po
                        | UnicodeCategory::Sm,
                ))
                .intervals()
        })
    });
    let exclude_categories = black_box(UnicodeCategory::Lu);
    let min_codepoint = black_box(0);
    let max_codepoint = black_box(128);
    c.bench_function("query - top level - only codepoints", |b| {
        b.iter(|| {
            version
                .query()
                .exclude_categories(exclude_categories)
                .min_codepoint(min_codepoint)
                .max_codepoint(max_codepoint)
                .intervals()
        })
    });
    c.bench_function("query - top level - exclude chars", |b| {
        b.iter(|| {
            version
                .query()
                .exclude_categories(exclude_categories)
                .exclude_characters(black_box("A@т"))
                .min_codepoint(min_codepoint)
                .max_codepoint(max_codepoint)
                .intervals()
        })
    });
    c.bench_function("query - top level - include and exclude chars", |b| {
        b.iter(|| {
            version
                .query()
                .exclude_categories(exclude_categories)
                .include_characters(black_box("0123456789"))
                .exclude_characters(black_box("QWERTYUIOP"))
                .min_codepoint(min_codepoint)
                .max_codepoint(max_codepoint)
                .intervals()
        })
    });
    c.bench_function("query - top level - include only", |b| {
        b.iter(|| {
            version
                .query()
                .include_categories(UnicodeCategory::Ll)
                .include_characters("ABC")
                .min_codepoint(0)
                .max_codepoint(50)
                .intervals()
        })
    });
    c.bench_function("query - single category - codepoint range", |b| {
        b.iter(|| {
            version
                .query()
                .include_categories(black_box(UnicodeCategory::Lo))
                .max_codepoint(black_box(0x1_0000))
                .intervals()
        })
    });
    // High `min_codepoint`: most of the category's intervals sit below the range and are
    // skipped by the binary search in `extend_clamped`.
    c.bench_function("query - single category - high range", |b| {
        b.iter(|| {
            version
                .query()
                .include_categories(black_box(UnicodeCategory::Lo))
                .min_codepoint(black_box(0x2_0000))
                .max_codepoint(black_box(0x2_4000))
                .intervals()
        })
    });
    c.bench_function("query - multiple categories - high range", |b| {
        b.iter(|| {
            version
                .query()
                .include_categories(black_box(UnicodeCategory::Lu | UnicodeCategory::Ll))
                .min_codepoint(black_box(0x1_0000))
                .max_codepoint(black_box(0x1_0500))
                .intervals()
        })
    });
    let interval_set = UnicodeVersion::V15_0_0
        .query()
        .include_categories(UnicodeCategory::UPPERCASE_LETTER)
        .interval_set()
        .expect("Invalid query input");
    c.bench_function("query - interval set - codepoint_at", |b| {
        b.iter(|| interval_set.codepoint_at(27))
    });
}

criterion_group!(default, version, intervals, categories, query);
criterion_main!(default);