Skip to content

Commit 5cd59a1

Browse files
committed
Use fancy-regex instead of onig
Use changes from fancy-regex lazy branch Prebuild bytesets and update benches Do not build a regexset for a single regex
1 parent 148fec0 commit 5cd59a1

20 files changed

Lines changed: 856 additions & 385 deletions

‎Cargo.toml‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "giallo"
3-
version = "0.5.2"
3+
version = "0.6.0"
44
edition = "2024"
55
license = "EUPL-1.2"
66
description = "A code highlighter giving the same output as VSCode"
@@ -19,17 +19,19 @@ include = [
1919

2020

2121
[dependencies]
22-
onig = { package = "onig-regset", version = "6", default-features = false }
2322
papaya = "0.2"
2423
serde = { version = "1", features = ["derive"] }
2524
serde_json = "1"
2625

2726
# Optional dependencies for serialization
2827
bitcode = { version = "0.6.9", optional = true, features = ["serde"] }
29-
zstd = { version = "0.13.3", optional = true }
30-
28+
zstd = { version = "0.14", optional = true }
29+
fancy-regex = "0.19"
3130
log = { version = "0.4", optional = true }
3231

32+
[patch.crates-io]
33+
fancy-regex = { git = "https://github.com/fancy-regex/fancy-regex" }
34+
3335
[features]
3436
tools = ["dump"]
3537
debug = ["dep:log"]

‎benches/highlight_benchmark.rs‎

Lines changed: 75 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
1-
use criterion::{Criterion, criterion_group, criterion_main};
2-
use giallo::{HighlightOptions, Registry, ThemeVariant};
31
use std::fs;
42

5-
fn highlight_jquery_benchmark(c: &mut Criterion) {
6-
// Load registry once for all benchmarks
7-
let mut registry =
8-
Registry::load_from_file("builtin.zst").expect("Failed to load registry from builtin.zst");
9-
registry.link_grammars();
10-
11-
// Read jQuery file content once
12-
let jquery_content =
13-
fs::read_to_string("src/fixtures/samples/jquery.js").expect("Failed to read jQuery file");
14-
15-
let options = HighlightOptions::new("javascript", ThemeVariant::Single("vitesse-black"));
3+
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
4+
use giallo::{HighlightOptions, Registry, ThemeVariant};
165

17-
c.bench_function("highlight jquery.js", |b| {
18-
b.iter(|| {
19-
registry.clear_pattern_cache();
20-
let result = registry.highlight(&jquery_content, &options).unwrap();
21-
std::hint::black_box(result);
22-
})
23-
});
6+
const SAMPLES: &[(&str, &str)] = &[
7+
("javascript", "src/fixtures/samples/jquery.js"),
8+
("javascript", "grammars-themes/samples/javascript.sample"),
9+
("typescript", "grammars-themes/samples/typescript.sample"),
10+
("tsx", "grammars-themes/samples/tsx.sample"),
11+
("rust", "grammars-themes/samples/rust.sample"),
12+
("c", "grammars-themes/samples/c.sample"),
13+
("markdown", "grammars-themes/samples/markdown.sample"),
14+
("python", "grammars-themes/samples/python.sample"),
15+
("html", "grammars-themes/samples/html.sample"),
16+
("css", "grammars-themes/samples/css.sample"),
17+
("ruby", "grammars-themes/samples/ruby.sample"),
18+
("go", "grammars-themes/samples/go.sample"),
19+
("astro", "grammars-themes/samples/astro.sample"),
20+
("c#", "grammars-themes/samples/csharp.sample"),
21+
("java", "grammars-themes/samples/java.sample"),
22+
("php", "grammars-themes/samples/php.sample"),
23+
("json", "grammars-themes/samples/json.sample"),
24+
("shellscript", "grammars-themes/samples/shellscript.sample"),
25+
];
26+
27+
fn bench_name(grammar: &'static str, path: &str) -> &'static str {
28+
if path.contains("jquery") {
29+
"jquery"
30+
} else {
31+
grammar
32+
}
2433
}
2534

2635
fn highlight_simple_benchmark(c: &mut Criterion) {
@@ -33,11 +42,11 @@ fn highlight_simple_benchmark(c: &mut Criterion) {
3342
let options = HighlightOptions::new("typescript", ThemeVariant::Single("vitesse-black"));
3443

3544
c.bench_function("highlight simple.ts", |b| {
36-
b.iter(|| {
37-
registry.clear_pattern_cache();
38-
let result = registry.highlight(&ts_content, &options).unwrap();
39-
std::hint::black_box(result);
40-
})
45+
b.iter_batched(
46+
|| registry.clear_caches(),
47+
|()| std::hint::black_box(registry.highlight(&ts_content, &options).unwrap()),
48+
BatchSize::PerIteration,
49+
)
4150
});
4251
}
4352

@@ -51,62 +60,57 @@ fn highlight_multiple_simple_benchmark(c: &mut Criterion) {
5160
let options = HighlightOptions::new("typescript", ThemeVariant::Single("vitesse-black"));
5261

5362
c.bench_function("highlight multiple simple.ts", |b| {
54-
b.iter(|| {
55-
// should not be 5x slower than "highlight simple.ts"
56-
registry.clear_pattern_cache();
57-
let result = registry.highlight(&ts_content, &options).unwrap();
58-
std::hint::black_box(result);
59-
let result = registry.highlight(&ts_content, &options).unwrap();
60-
std::hint::black_box(result);
61-
let result = registry.highlight(&ts_content, &options).unwrap();
62-
std::hint::black_box(result);
63-
let result = registry.highlight(&ts_content, &options).unwrap();
64-
std::hint::black_box(result);
65-
let result = registry.highlight(&ts_content, &options).unwrap();
66-
std::hint::black_box(result);
67-
})
63+
b.iter_batched(
64+
|| registry.clear_caches(),
65+
|()| {
66+
// should not be 5x slower than "highlight simple.ts"
67+
for _ in 0..5 {
68+
std::hint::black_box(registry.highlight(&ts_content, &options).unwrap());
69+
}
70+
},
71+
BatchSize::PerIteration,
72+
)
6873
});
6974
}
7075

71-
fn highlight_sample_benchmark(c: &mut Criterion, grammar: &str) {
72-
let mut registry =
73-
Registry::load_from_file("builtin.zst").expect("Failed to load registry from builtin.zst");
74-
registry.link_grammars();
75-
76-
let sample_path = format!("grammars-themes/samples/{grammar}.sample");
77-
let content = fs::read_to_string(&sample_path)
78-
.unwrap_or_else(|_| panic!("Failed to read sample {sample_path}"));
79-
80-
let options = HighlightOptions::new(grammar, ThemeVariant::Single("vitesse-black"));
81-
82-
c.bench_function(&format!("highlight {grammar}.sample"), |b| {
83-
b.iter(|| {
84-
registry.clear_pattern_cache();
85-
let result = registry.highlight(&content, &options).unwrap();
86-
std::hint::black_box(result);
87-
})
88-
});
89-
}
90-
91-
fn highlight_rust_sample_benchmark(c: &mut Criterion) {
92-
highlight_sample_benchmark(c, "rust");
93-
}
94-
95-
fn highlight_markdown_sample_benchmark(c: &mut Criterion) {
96-
highlight_sample_benchmark(c, "markdown");
76+
fn highlight_cold_benchmark(c: &mut Criterion) {
77+
let registry = Registry::load_from_file("builtin.zst").unwrap();
78+
let mut group = c.benchmark_group("highlight cold");
79+
group.sample_size(50);
80+
for &(grammar, path) in SAMPLES {
81+
let content = fs::read_to_string(path).unwrap();
82+
let options = HighlightOptions::new(grammar, ThemeVariant::Single("vitesse-black"));
83+
registry.highlight(&content, &options).unwrap();
84+
group.bench_function(bench_name(grammar, path), |b| {
85+
b.iter_batched(
86+
|| registry.clear_caches(),
87+
|()| std::hint::black_box(registry.highlight(&content, &options).unwrap()),
88+
BatchSize::PerIteration,
89+
)
90+
});
91+
}
92+
group.finish();
9793
}
9894

99-
fn highlight_javascript_sample_benchmark(c: &mut Criterion) {
100-
highlight_sample_benchmark(c, "javascript");
95+
fn highlight_warm_benchmark(c: &mut Criterion) {
96+
let registry = Registry::load_from_file("builtin.zst").unwrap();
97+
let mut group = c.benchmark_group("highlight warm");
98+
for &(grammar, path) in SAMPLES {
99+
let content = fs::read_to_string(path).unwrap();
100+
let options = HighlightOptions::new(grammar, ThemeVariant::Single("vitesse-black"));
101+
registry.highlight(&content, &options).unwrap();
102+
group.bench_function(bench_name(grammar, path), |b| {
103+
b.iter(|| std::hint::black_box(registry.highlight(&content, &options).unwrap()))
104+
});
105+
}
106+
group.finish();
101107
}
102108

103109
criterion_group!(
104110
benches,
105-
highlight_jquery_benchmark,
106111
highlight_simple_benchmark,
107112
highlight_multiple_simple_benchmark,
108-
highlight_rust_sample_benchmark,
109-
highlight_markdown_sample_benchmark,
110-
highlight_javascript_sample_benchmark
113+
highlight_cold_benchmark,
114+
highlight_warm_benchmark,
111115
);
112116
criterion_main!(benches);

‎examples/html_classes.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use giallo::{HighlightOptions, HtmlRenderer, Registry, RenderOptions, ThemeVariant};
22
use std::fs;
33

4+
#[allow(clippy::single_range_in_vec_init)]
45
fn main() -> Result<(), Box<dyn std::error::Error>> {
56
let mut registry = Registry::load_from_file("builtin.zst")?;
67
registry.link_grammars();

‎grammars-themes‎

Submodule grammars-themes updated 55 files
Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use onig::SearchOptions;
21
use std::fmt;
32

43
/// We use that as a way to convey both the rule and which anchors should be active
@@ -36,15 +35,21 @@ impl AnchorActive {
3635
}
3736
}
3837

39-
pub fn to_search_options(self) -> SearchOptions {
38+
pub fn allow_a(&self) -> bool {
39+
matches!(self, AnchorActive::A | AnchorActive::AG)
40+
}
41+
42+
pub fn allow_g(&self) -> bool {
43+
matches!(self, AnchorActive::G | AnchorActive::AG)
44+
}
45+
46+
/// The same anchor context with \G deactivated.
47+
/// RegexSet handles it on its own (inside fancy-regex) but not the normal Regex
48+
/// we walk through
49+
pub fn without_g(&self) -> Self {
4050
match self {
41-
AnchorActive::AG => SearchOptions::SEARCH_OPTION_NONE,
42-
AnchorActive::A => SearchOptions::SEARCH_OPTION_NOT_BEGIN_POSITION,
43-
AnchorActive::G => SearchOptions::SEARCH_OPTION_NOT_BEGIN_STRING,
44-
AnchorActive::None => {
45-
SearchOptions::SEARCH_OPTION_NOT_BEGIN_STRING
46-
| SearchOptions::SEARCH_OPTION_NOT_BEGIN_POSITION
47-
}
51+
AnchorActive::A | AnchorActive::AG => AnchorActive::A,
52+
AnchorActive::G | AnchorActive::None => AnchorActive::None,
4853
}
4954
}
5055
}

‎src/grammars/caches.rs‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use std::sync::{Arc, OnceLock};
2+
3+
use crate::grammars::engine;
4+
5+
/// We want to compile only one of each regex/regexset as they are expensive.
6+
/// We key by the patterns so they can be reused in multiple grammars/context.
7+
#[derive(Default, Debug)]
8+
pub(crate) struct RegexCache {
9+
/// We cache the fancy regexes across all grammars
10+
regexes: papaya::HashMap<String, OnceLock<Arc<engine::Regex>>>,
11+
/// And the sets are cached by the patterns
12+
sets: papaya::HashMap<Vec<String>, OnceLock<Arc<engine::RegexSet>>>,
13+
}
14+
15+
impl RegexCache {
16+
#[doc(hidden)]
17+
pub(crate) fn clear(&self) {
18+
self.sets.pin().clear();
19+
self.regexes.pin().clear();
20+
}
21+
22+
pub fn get_regex(&self, pattern: &str) -> Arc<engine::Regex> {
23+
self.regexes
24+
.pin()
25+
.get_or_insert_with(pattern.to_string(), OnceLock::new)
26+
.get_or_init(|| Arc::new(engine::Regex::new(pattern)))
27+
.clone()
28+
}
29+
30+
pub fn get_set(&self, patterns: &[String]) -> Arc<engine::RegexSet> {
31+
self.sets
32+
.pin()
33+
.get_or_insert_with(patterns.to_vec(), OnceLock::new)
34+
.get_or_init(|| {
35+
let regexes: Vec<_> = patterns.iter().map(|p| self.get_regex(p)).collect();
36+
Arc::new(engine::RegexSet::from_regexes(&regexes))
37+
})
38+
.clone()
39+
}
40+
}

0 commit comments

Comments
 (0)