1use std::cmp::{min, Ordering};
3use std::collections::HashMap;
4use std::fmt;
5use std::mem;
6use std::str::FromStr;
7use std::sync::{Mutex, MutexGuard};
8
9use serde::de::{Deserialize, Deserializer, Error, Visitor};
10use serde::ser::{Serialize, Serializer};
11use serde_derive::{Deserialize, Serialize};
12use std::sync::LazyLock;
13
14#[derive(Debug, thiserror::Error)]
16#[non_exhaustive]
17pub enum ScopeError {
18 #[error("Tried to restore cleared scopes, but none were cleared")]
19 NoClearedScopesToRestore,
20}
21
22pub const ATOM_LEN_BITS: u16 = 3;
27
28#[deprecated(
34 since = "5.3.0",
35 note = "\
36 Deprecated in anticipation of removal in the next semver-breaking release under the \
37 justification that it's incredibly niche functionality to expose. If you rely on this \
38 functionality then please express your particular use-case in the github issue: \
39 https://github.com/trishume/syntect/issues/575\
40 "
41)]
42pub static SCOPE_REPO: LazyLock<Mutex<ScopeRepository>> =
43 LazyLock::new(|| Mutex::new(ScopeRepository::new()));
44
45pub(crate) fn lock_global_scope_repo() -> MutexGuard<'static, ScopeRepository> {
46 #[allow(deprecated)]
47 SCOPE_REPO.lock().unwrap()
48}
49
50#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Default, Hash)]
66pub struct Scope {
67 a: u64,
68 b: u64,
69}
70
71#[derive(Debug, thiserror::Error)]
73#[non_exhaustive]
74pub enum ParseScopeError {
75 #[error("Too long scope. Scopes can be at most 8 atoms long.")]
78 TooLong,
79 #[error("Too many atoms. Max 2^16-2 atoms allowed.")]
82 TooManyAtoms,
83}
84
85#[derive(Debug)]
97pub struct ScopeRepository {
98 atoms: Vec<String>,
99 atom_index_map: HashMap<String, usize>,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
114pub struct ScopeStack {
115 clear_stack: Vec<Vec<Scope>>,
116 pub scopes: Vec<Scope>,
117}
118
119#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
120pub enum ClearAmount {
121 TopN(usize),
122 All,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum ScopeStackOp {
135 Push(Scope),
136 Pop(usize),
137 Clear(ClearAmount),
139 Restore,
141 Noop,
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
148pub enum BasicScopeStackOp {
149 Push(Scope),
150 Pop,
151}
152
153fn pack_as_u16s(atoms: &[usize]) -> Result<Scope, ParseScopeError> {
154 let mut res = Scope { a: 0, b: 0 };
155
156 for (i, &n) in atoms.iter().enumerate() {
157 if n >= (u16::MAX as usize) - 2 {
158 return Err(ParseScopeError::TooManyAtoms);
159 }
160 let small = (n + 1) as u64; if i < 4 {
163 let shift = (3 - i) * 16;
164 res.a |= small << shift;
165 } else {
166 let shift = (7 - i) * 16;
167 res.b |= small << shift;
168 }
169 }
170 Ok(res)
171}
172
173impl ScopeRepository {
174 fn new() -> ScopeRepository {
175 ScopeRepository {
176 atoms: Vec::new(),
177 atom_index_map: HashMap::new(),
178 }
179 }
180
181 pub fn build(&mut self, s: &str) -> Result<Scope, ParseScopeError> {
182 if s.is_empty() {
183 return Ok(Scope { a: 0, b: 0 });
184 }
185 let parts: Vec<usize> = s
186 .trim_end_matches('.')
187 .split('.')
188 .map(|a| self.atom_to_index(a))
189 .collect();
190 let len = parts.len().min(8);
195 pack_as_u16s(&parts[..len])
196 }
197
198 pub fn to_string(&self, scope: Scope) -> String {
199 let mut s = String::new();
200 for i in 0..8 {
201 let atom_number = scope.atom_at(i);
202 if atom_number == 0 {
205 break;
206 }
207 if i != 0 {
208 s.push('.');
209 }
210 s.push_str(self.atom_str(atom_number));
211 }
212 s
213 }
214
215 fn atom_to_index(&mut self, atom: &str) -> usize {
216 if let Some(index) = self.atom_index_map.get(atom) {
217 return *index;
218 }
219
220 self.atoms.push(atom.to_owned());
221 let index = self.atoms.len() - 1;
222 self.atom_index_map.insert(atom.to_owned(), index);
223
224 index
225 }
226
227 pub fn atom_str(&self, atom_number: u16) -> &str {
231 &self.atoms[(atom_number - 1) as usize]
232 }
233}
234
235impl Scope {
236 pub fn new(s: &str) -> Result<Scope, ParseScopeError> {
240 let mut repo = lock_global_scope_repo();
241 repo.build(s.trim())
242 }
243
244 pub fn atom_at(self, index: usize) -> u16 {
249 #[allow(clippy::panic)]
250 let shifted = if index < 4 {
252 self.a >> ((3 - index) * 16)
253 } else if index < 8 {
254 self.b >> ((7 - index) * 16)
255 } else {
256 panic!("atom index out of bounds {:?}", index);
257 };
258 (shifted & 0xFFFF) as u16
259 }
260
261 #[inline]
262 fn missing_atoms(self) -> u32 {
263 let trail = if self.b == 0 {
264 self.a.trailing_zeros() + 64
265 } else {
266 self.b.trailing_zeros()
267 };
268 trail / 16
269 }
270
271 #[inline(always)]
273 pub fn len(self) -> u32 {
274 8 - self.missing_atoms()
275 }
276
277 pub fn is_empty(self) -> bool {
278 self.len() == 0
279 }
280
281 pub fn build_string(self) -> String {
285 let repo = lock_global_scope_repo();
286 repo.to_string(self)
287 }
288
289 pub fn is_prefix_of(self, s: Scope) -> bool {
313 let pref_missing = self.missing_atoms();
314
315 let mask: (u64, u64) = if pref_missing == 8 {
317 (0, 0)
318 } else if pref_missing == 4 {
319 (u64::MAX, 0)
320 } else if pref_missing > 4 {
321 (u64::MAX << ((pref_missing - 4) * 16), 0)
322 } else {
323 (u64::MAX, u64::MAX << (pref_missing * 16))
324 };
325
326 let ax = (self.a ^ s.a) & mask.0;
328 let bx = (self.b ^ s.b) & mask.1;
329 ax == 0 && bx == 0
333 }
334}
335
336impl FromStr for Scope {
337 type Err = ParseScopeError;
338
339 fn from_str(s: &str) -> Result<Scope, ParseScopeError> {
340 Scope::new(s)
341 }
342}
343
344impl fmt::Display for Scope {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 let s = self.build_string();
347 write!(f, "{}", s)
348 }
349}
350
351impl fmt::Debug for Scope {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353 let s = self.build_string();
354 write!(f, "<{}>", s)
355 }
356}
357
358impl Serialize for Scope {
359 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
360 where
361 S: Serializer,
362 {
363 let s = self.build_string();
364 serializer.serialize_str(&s)
365 }
366}
367
368impl<'de> Deserialize<'de> for Scope {
369 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
370 where
371 D: Deserializer<'de>,
372 {
373 struct ScopeVisitor;
374
375 impl Visitor<'_> for ScopeVisitor {
376 type Value = Scope;
377
378 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
379 formatter.write_str("a string")
380 }
381
382 fn visit_str<E>(self, v: &str) -> Result<Scope, E>
383 where
384 E: Error,
385 {
386 Scope::new(v).map_err(|e| Error::custom(format!("Invalid scope: {:?}", e)))
387 }
388 }
389
390 deserializer.deserialize_str(ScopeVisitor)
391 }
392}
393
394#[derive(Debug, Copy, Clone, PartialEq)]
397pub struct MatchPower(pub f64);
398
399impl Eq for MatchPower {}
400
401impl Ord for MatchPower {
402 fn cmp(&self, other: &Self) -> Ordering {
403 self.0.partial_cmp(&other.0).unwrap()
404 }
405}
406
407impl PartialOrd for MatchPower {
408 fn partial_cmp(&self, other: &MatchPower) -> Option<Ordering> {
409 Some(self.cmp(other))
410 }
411}
412
413impl ScopeStack {
414 pub fn new() -> ScopeStack {
415 ScopeStack {
416 clear_stack: Vec::new(),
417 scopes: Vec::new(),
418 }
419 }
420
421 pub fn from_vec(v: Vec<Scope>) -> ScopeStack {
424 ScopeStack {
425 clear_stack: Vec::new(),
426 scopes: v,
427 }
428 }
429
430 #[inline]
431 pub fn push(&mut self, s: Scope) {
432 self.scopes.push(s);
433 }
434
435 #[inline]
436 pub fn pop(&mut self) {
437 self.scopes.pop();
438 }
439
440 pub fn apply(&mut self, op: &ScopeStackOp) -> Result<(), ScopeError> {
444 self.apply_with_hook(op, |_, _| {})
445 }
446
447 #[inline]
455 pub fn apply_with_hook<F>(&mut self, op: &ScopeStackOp, mut hook: F) -> Result<(), ScopeError>
456 where
457 F: FnMut(BasicScopeStackOp, &[Scope]),
458 {
459 match *op {
460 ScopeStackOp::Push(scope) => {
461 self.scopes.push(scope);
462 hook(BasicScopeStackOp::Push(scope), self.as_slice());
463 }
464 ScopeStackOp::Pop(count) => {
465 for _ in 0..count {
466 self.scopes.pop();
467 hook(BasicScopeStackOp::Pop, self.as_slice());
468 }
469 }
470 ScopeStackOp::Clear(amount) => {
471 let cleared = match amount {
472 ClearAmount::TopN(n) => {
473 let to_leave = self.scopes.len() - min(n, self.scopes.len());
475 self.scopes.split_off(to_leave)
476 }
477 ClearAmount::All => {
478 let mut cleared = Vec::new();
479 mem::swap(&mut cleared, &mut self.scopes);
480 cleared
481 }
482 };
483 let clear_amount = cleared.len();
484 self.clear_stack.push(cleared);
485 for _ in 0..clear_amount {
486 hook(BasicScopeStackOp::Pop, self.as_slice());
487 }
488 }
489 ScopeStackOp::Restore => match self.clear_stack.pop() {
490 Some(ref mut to_push) => {
491 for s in to_push {
492 self.scopes.push(*s);
493 hook(BasicScopeStackOp::Push(*s), self.as_slice());
494 }
495 }
496 None => return Err(ScopeError::NoClearedScopesToRestore),
497 },
498 ScopeStackOp::Noop => (),
499 }
500
501 Ok(())
502 }
503
504 pub fn debug_print(&self, repo: &ScopeRepository) {
507 for s in &self.scopes {
508 print!("{} ", repo.to_string(*s));
509 }
510 println!();
511 }
512
513 pub fn bottom_n(&self, n: usize) -> &[Scope] {
517 &self.scopes[0..n]
518 }
519
520 #[inline]
522 pub fn as_slice(&self) -> &[Scope] {
523 &self.scopes[..]
524 }
525
526 #[inline]
528 pub fn len(&self) -> usize {
529 self.scopes.len()
530 }
531
532 #[inline]
533 pub fn is_empty(&self) -> bool {
534 self.len() == 0
535 }
536
537 pub fn does_match(&self, stack: &[Scope]) -> Option<MatchPower> {
559 let mut sel_index: usize = 0;
560 let mut score: f64 = 0.0;
561 for (i, scope) in stack.iter().enumerate() {
562 let sel_scope = self.scopes[sel_index];
563 if sel_scope.is_prefix_of(*scope) {
564 let len = sel_scope.len();
565 score += f64::from(len) * f64::from(ATOM_LEN_BITS * (i as u16)).exp2();
567 sel_index += 1;
568 if sel_index >= self.scopes.len() {
569 return Some(MatchPower(score));
570 }
571 }
572 }
573 None
574 }
575}
576
577impl FromStr for ScopeStack {
578 type Err = ParseScopeError;
579
580 fn from_str(s: &str) -> Result<ScopeStack, ParseScopeError> {
582 let mut scopes = Vec::new();
583 for name in s.split_whitespace() {
584 scopes.push(Scope::from_str(name)?)
585 }
586 Ok(ScopeStack::from_vec(scopes))
587 }
588}
589
590impl fmt::Display for ScopeStack {
591 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
592 for s in &self.scopes {
593 write!(f, "{} ", s)?;
594 }
595 Ok(())
596 }
597}
598
599#[cfg(test)]
600mod tests {
601 use super::*;
602
603 #[test]
604 fn misc() {
605 }
611
612 #[test]
613 fn repo_works() {
614 let mut repo = ScopeRepository::new();
615 assert_eq!(
616 repo.build("source.php").unwrap(),
617 repo.build("source.php").unwrap()
618 );
619 assert_eq!(
620 repo.build("source.php.wow.hi.bob.troll.clock.5").unwrap(),
621 repo.build("source.php.wow.hi.bob.troll.clock.5").unwrap()
622 );
623 assert_eq!(repo.build("").unwrap(), repo.build("").unwrap());
624 let s1 = repo.build("").unwrap();
625 assert_eq!(repo.to_string(s1), "");
626 let s2 = repo.build("source.php.wow").unwrap();
627 assert_eq!(repo.to_string(s2), "source.php.wow");
628 assert!(repo.build("source.php").unwrap() != repo.build("source.perl").unwrap());
629 assert!(repo.build("source.php").unwrap() != repo.build("source.php.wagon").unwrap());
630 assert_eq!(
631 repo.build("comment.line.").unwrap(),
632 repo.build("comment.line").unwrap()
633 );
634 }
635
636 #[test]
637 fn global_repo_works() {
638 use std::str::FromStr;
639 assert_eq!(
640 Scope::new("source.php").unwrap(),
641 Scope::new("source.php").unwrap()
642 );
643 assert!(Scope::from_str("1.2.3.4.5.6.7.8").is_ok());
644 let nine = Scope::from_str("1.2.3.4.5.6.7.8.9").unwrap();
648 let eight = Scope::from_str("1.2.3.4.5.6.7.8").unwrap();
649 assert_eq!(nine, eight);
650 }
651
652 #[test]
653 fn prefixes_work() {
654 assert!(Scope::new("1.2.3.4.5.6.7.8")
655 .unwrap()
656 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
657 assert!(Scope::new("1.2.3.4.5.6")
658 .unwrap()
659 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
660 assert!(Scope::new("1.2.3.4")
661 .unwrap()
662 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
663 assert!(!Scope::new("1.2.3.4.5.6.a")
664 .unwrap()
665 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
666 assert!(!Scope::new("1.2.a.4.5.6.7")
667 .unwrap()
668 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
669 assert!(!Scope::new("1.2.a.4.5.6.7")
670 .unwrap()
671 .is_prefix_of(Scope::new("1.2.3.4.5").unwrap()));
672 assert!(!Scope::new("1.2.a")
673 .unwrap()
674 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
675 }
676
677 #[test]
678 fn matching_works() {
679 use std::str::FromStr;
680 assert_eq!(
681 ScopeStack::from_str("string")
682 .unwrap()
683 .does_match(ScopeStack::from_str("string.quoted").unwrap().as_slice()),
684 Some(MatchPower(0o1u64 as f64))
685 );
686 assert_eq!(
687 ScopeStack::from_str("source")
688 .unwrap()
689 .does_match(ScopeStack::from_str("string.quoted").unwrap().as_slice()),
690 None
691 );
692 assert_eq!(
693 ScopeStack::from_str("a.b e.f")
694 .unwrap()
695 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
696 Some(MatchPower(0o202u64 as f64))
697 );
698 assert_eq!(
699 ScopeStack::from_str("c e.f")
700 .unwrap()
701 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
702 Some(MatchPower(0o210u64 as f64))
703 );
704 assert_eq!(
705 ScopeStack::from_str("c.d e.f")
706 .unwrap()
707 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
708 Some(MatchPower(0o220u64 as f64))
709 );
710 assert_eq!(
711 ScopeStack::from_str("a.b c e.f")
712 .unwrap()
713 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
714 Some(MatchPower(0o212u64 as f64))
715 );
716 assert_eq!(
717 ScopeStack::from_str("a c.d")
718 .unwrap()
719 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
720 Some(MatchPower(0o021u64 as f64))
721 );
722 assert_eq!(
723 ScopeStack::from_str("a c.d.e")
724 .unwrap()
725 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
726 None
727 );
728 }
729}