Skip to content

Commit 3bf924d

Browse files
committed
Use fancy instead of onig
1 parent 2725453 commit 3bf924d

13 files changed

Lines changed: 376 additions & 364 deletions

File tree

‎Cargo.toml‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ 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"] }
2928
zstd = { version = "0.13.3", optional = true }
30-
29+
fancy-regex = {git="https://github.com/fancy-regex/fancy-regex/", branch = "main"}
3130
log = { version = "0.4", optional = true }
3231

3332
[features]

‎benches/highlight_benchmark.rs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn highlight_jquery_benchmark(c: &mut Criterion) {
1616

1717
c.bench_function("highlight jquery.js", |b| {
1818
b.iter(|| {
19-
registry.clear_pattern_cache();
19+
registry.clear_caches();
2020
let result = registry.highlight(&jquery_content, &options).unwrap();
2121
std::hint::black_box(result);
2222
})
@@ -34,7 +34,7 @@ fn highlight_simple_benchmark(c: &mut Criterion) {
3434

3535
c.bench_function("highlight simple.ts", |b| {
3636
b.iter(|| {
37-
registry.clear_pattern_cache();
37+
registry.clear_caches();
3838
let result = registry.highlight(&ts_content, &options).unwrap();
3939
std::hint::black_box(result);
4040
})
@@ -53,7 +53,7 @@ fn highlight_multiple_simple_benchmark(c: &mut Criterion) {
5353
c.bench_function("highlight multiple simple.ts", |b| {
5454
b.iter(|| {
5555
// should not be 5x slower than "highlight simple.ts"
56-
registry.clear_pattern_cache();
56+
registry.clear_caches();
5757
let result = registry.highlight(&ts_content, &options).unwrap();
5858
std::hint::black_box(result);
5959
let result = registry.highlight(&ts_content, &options).unwrap();
@@ -81,7 +81,7 @@ fn highlight_sample_benchmark(c: &mut Criterion, grammar: &str) {
8181

8282
c.bench_function(&format!("highlight {grammar}.sample"), |b| {
8383
b.iter(|| {
84-
registry.clear_pattern_cache();
84+
registry.clear_caches();
8585
let result = registry.highlight(&content, &options).unwrap();
8686
std::hint::black_box(result);
8787
})

‎src/caches.rs‎

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

‎src/grammars/compiled.rs‎

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ use std::sync::LazyLock;
55
use serde::{Deserialize, Serialize};
66

77
use crate::grammars::injections::{CompiledInjectionMatcher, parse_injection_selector};
8+
use crate::grammars::pattern::Pattern;
89
use crate::grammars::raw::{Captures, RawGrammar, RawRule, Reference};
9-
use crate::grammars::regex::Regex;
1010
use crate::scope::Scope;
1111

12-
static CAPTURING_NAME_RE: LazyLock<onig::Regex> =
13-
LazyLock::new(|| onig::Regex::new(r"\$(\d+)|\${(\d+):\/(downcase|upcase)}").unwrap());
12+
static CAPTURING_NAME_RE: LazyLock<fancy_regex::Regex> =
13+
LazyLock::new(|| fancy_regex::Regex::new(r"\$(\d+)|\${(\d+):\/(downcase|upcase)}").unwrap());
1414

1515
fn has_captures(pat: Option<&str>) -> bool {
1616
if let Some(p) = pat {
17-
CAPTURING_NAME_RE.find(p).is_some()
17+
matches!(CAPTURING_NAME_RE.find(p), Ok(Some(_)))
1818
} else {
1919
false
2020
}
@@ -46,14 +46,15 @@ pub fn replace_captures(
4646
captures_pos: &[Option<(usize, usize)>],
4747
) -> String {
4848
CAPTURING_NAME_RE
49-
.replace_all(original_name, |caps: &onig::Captures| {
49+
.replace_all(original_name, |caps: &fancy_regex::Captures<str>| {
5050
let capture_num = caps
51-
.at(1)
52-
.or_else(|| caps.at(2))
51+
.get(1)
52+
.or_else(|| caps.get(2))
53+
.map(|m| m.as_str())
5354
.unwrap_or("0")
5455
.parse::<usize>()
5556
.unwrap_or(0);
56-
let command = caps.at(3);
57+
let command = caps.get(3).map(|m| m.as_str());
5758

5859
if let Some(Some((start, end))) = captures_pos.get(capture_num) {
5960
// Remove leading dots that would make the selector invalid
@@ -68,7 +69,7 @@ pub fn replace_captures(
6869
String::new()
6970
} else {
7071
// Invalid capture bounds (index out of bounds), return original match
71-
caps.at(0).unwrap().to_string()
72+
caps.get(0).unwrap().as_str().to_string()
7273
}
7374
})
7475
.to_string()
@@ -135,9 +136,9 @@ pub const PRE_CROSS_LINKING_RULE_REF: GlobalRuleRef = GlobalRuleRef {
135136

136137
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
137138
#[serde(transparent)]
138-
pub struct RegexId(u16);
139+
pub struct PatternId(u16);
139140

140-
impl RegexId {
141+
impl PatternId {
141142
pub(crate) fn as_index(self) -> usize {
142143
self.0 as usize
143144
}
@@ -192,10 +193,10 @@ pub struct Match {
192193
pub name: Option<String>,
193194
pub name_is_capturing: bool,
194195
pub scopes: Vec<Scope>,
195-
/// The regex ID for this match rule.
196+
/// The pattern ID for this match rule.
196197
/// None for scope-only rules (e.g., capture groups that only assign scopes like
197198
/// punctuation.definition.string.begin without their own pattern to match)
198-
pub regex_id: Option<RegexId>,
199+
pub pattern_id: Option<PatternId>,
199200
pub captures: Vec<Option<GlobalRuleRef>>,
200201
pub repository_stack: RepositoryStack,
201202
}
@@ -222,9 +223,9 @@ pub struct BeginEnd {
222223
pub content_name: Option<String>,
223224
pub content_name_is_capturing: bool,
224225
pub content_scopes: Vec<Scope>,
225-
pub begin: RegexId,
226+
pub begin: PatternId,
226227
pub begin_captures: Vec<Option<GlobalRuleRef>>,
227-
pub end: RegexId,
228+
pub end: PatternId,
228229
pub end_has_backrefs: bool,
229230
pub end_captures: Vec<Option<GlobalRuleRef>>,
230231
pub apply_end_pattern_last: bool,
@@ -241,9 +242,9 @@ pub struct BeginWhile {
241242
pub content_name: Option<String>,
242243
pub content_name_is_capturing: bool,
243244
pub content_scopes: Vec<Scope>,
244-
pub begin: RegexId,
245+
pub begin: PatternId,
245246
pub begin_captures: Vec<Option<GlobalRuleRef>>,
246-
pub while_: RegexId,
247+
pub while_: PatternId,
247248
pub while_has_backrefs: bool,
248249
pub while_captures: Vec<Option<GlobalRuleRef>>,
249250
pub patterns: Vec<GlobalRuleRef>,
@@ -421,7 +422,7 @@ pub struct CompiledGrammar {
421422
pub scope_name: String,
422423
pub scope: Scope,
423424
pub file_types: Vec<String>,
424-
pub regexes: Vec<Regex>,
425+
pub patterns: Vec<Pattern>,
425426
pub rules: Vec<Rule>,
426427
pub repositories: Vec<Repository>,
427428
pub injections: Vec<(Vec<CompiledInjectionMatcher>, GlobalRuleRef)>,
@@ -442,7 +443,7 @@ impl CompiledGrammar {
442443
scope_name: raw.scope_name.clone(),
443444
scope: Scope::new(&raw.scope_name)[0],
444445
file_types: raw.file_types,
445-
regexes: Vec::new(),
446+
patterns: Vec::new(),
446447
rules: Vec::new(),
447448
repositories: Vec::new(),
448449
injections: Vec::new(),
@@ -515,7 +516,7 @@ impl CompiledGrammar {
515516
name_is_capturing,
516517
name,
517518
scopes,
518-
regex_id: Some(self.compile_regex(pat).0),
519+
pattern_id: Some(self.compile_regex(pat).0),
519520
captures: self.compile_captures(raw_rule.captures, repository_stack),
520521
repository_stack,
521522
})
@@ -625,7 +626,7 @@ impl CompiledGrammar {
625626
name_is_capturing,
626627
name,
627628
scopes,
628-
regex_id: None, // Scope-only rule (e.g., capture that only assigns scope)
629+
pattern_id: None, // Scope-only rule (e.g., capture that only assigns scope)
629630
captures: vec![],
630631
repository_stack,
631632
})
@@ -685,13 +686,12 @@ impl CompiledGrammar {
685686
local_id
686687
}
687688

688-
fn compile_regex(&mut self, pattern: String) -> (RegexId, bool) {
689-
let regex_id = RegexId(self.regexes.len() as u16);
689+
fn compile_regex(&mut self, pattern: String) -> (PatternId, bool) {
690+
let pat_id = PatternId(self.patterns.len() as u16);
690691
let has_backrefs = has_backreferences(&pattern);
691-
let re = Regex::new(pattern);
692-
self.regexes.push(re);
692+
self.patterns.push(Pattern::new(pattern));
693693

694-
(regex_id, has_backrefs)
694+
(pat_id, has_backrefs)
695695
}
696696

697697
fn compile_repository(
@@ -969,16 +969,16 @@ impl IndexMut<RuleId> for Vec<Rule> {
969969
}
970970
}
971971

972-
impl Index<RegexId> for Vec<Regex> {
973-
type Output = Regex;
972+
impl Index<PatternId> for Vec<Pattern> {
973+
type Output = Pattern;
974974

975-
fn index(&self, index: RegexId) -> &Self::Output {
975+
fn index(&self, index: PatternId) -> &Self::Output {
976976
&self[index.as_index()]
977977
}
978978
}
979979

980-
impl IndexMut<RegexId> for Vec<Regex> {
981-
fn index_mut(&mut self, index: RegexId) -> &mut Self::Output {
980+
impl IndexMut<PatternId> for Vec<Pattern> {
981+
fn index_mut(&mut self, index: PatternId) -> &mut Self::Output {
982982
&mut self[index.as_index()]
983983
}
984984
}

‎src/grammars/injections.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize};
66
use crate::scope::Scope;
77

88
/// Regex for tokenizing injection selectors (matches vscode-textmate exactly except for \* added)
9-
static TOKEN_REGEX: LazyLock<onig::Regex> = LazyLock::new(|| {
10-
onig::Regex::new(r"([LR]:|[\w.:]+[\w\*.:\-]*|[,|\-()])").expect("Invalid selector regex")
9+
static TOKEN_REGEX: LazyLock<fancy_regex::Regex> = LazyLock::new(|| {
10+
fancy_regex::Regex::new(r"([LR]:|[\w.:]+[\w\*.:\-]*|[,|\-()])").expect("Invalid selector regex")
1111
});
1212

1313
// Only Left matters, Right is the same as no precedence. We keep both just for debug reasons
@@ -246,7 +246,7 @@ pub fn parse_injection_selector(selector: &str) -> Vec<CompiledInjectionMatcher>
246246

247247
let tokens: Vec<_> = TOKEN_REGEX
248248
.find_iter(selector)
249-
.map(|(start, end)| &selector[start..end])
249+
.map(|m| m.expect("Failed to match injection selector").as_str())
250250
.filter(|s| !s.is_empty())
251251
.collect();
252252

‎src/grammars/mod.rs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
mod compiled;
22
mod injections;
3+
pub mod pattern;
34
mod pattern_set;
45
mod raw;
5-
mod regex;
6+
pub(crate) mod regex;
67

78
pub use compiled::*;
89
pub use injections::InjectionPrecedence;
10+
pub use pattern::resolve_backreferences;
911
pub use pattern_set::{PatternSet, PatternSetMatch};
1012
pub use raw::RawGrammar;
11-
pub use regex::{Regex, resolve_backreferences};

0 commit comments

Comments
 (0)