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 once_cell::sync::Lazy;
10use serde::de::{Deserialize, Deserializer, Error, Visitor};
11use serde::ser::{Serialize, Serializer};
12use serde_derive::{Deserialize, Serialize};
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: Lazy<Mutex<ScopeRepository>> =
43 Lazy::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 if parts.len() > 8 {
191 return Err(ParseScopeError::TooManyAtoms);
192 }
193 pack_as_u16s(&parts[..])
194 }
195
196 pub fn to_string(&self, scope: Scope) -> String {
197 let mut s = String::new();
198 for i in 0..8 {
199 let atom_number = scope.atom_at(i);
200 if atom_number == 0 {
203 break;
204 }
205 if i != 0 {
206 s.push('.');
207 }
208 s.push_str(self.atom_str(atom_number));
209 }
210 s
211 }
212
213 fn atom_to_index(&mut self, atom: &str) -> usize {
214 if let Some(index) = self.atom_index_map.get(atom) {
215 return *index;
216 }
217
218 self.atoms.push(atom.to_owned());
219 let index = self.atoms.len() - 1;
220 self.atom_index_map.insert(atom.to_owned(), index);
221
222 index
223 }
224
225 pub fn atom_str(&self, atom_number: u16) -> &str {
229 &self.atoms[(atom_number - 1) as usize]
230 }
231}
232
233impl Scope {
234 pub fn new(s: &str) -> Result<Scope, ParseScopeError> {
238 let mut repo = lock_global_scope_repo();
239 repo.build(s.trim())
240 }
241
242 pub fn atom_at(self, index: usize) -> u16 {
247 #[allow(clippy::panic)]
248 let shifted = if index < 4 {
250 self.a >> ((3 - index) * 16)
251 } else if index < 8 {
252 self.b >> ((7 - index) * 16)
253 } else {
254 panic!("atom index out of bounds {:?}", index);
255 };
256 (shifted & 0xFFFF) as u16
257 }
258
259 #[inline]
260 fn missing_atoms(self) -> u32 {
261 let trail = if self.b == 0 {
262 self.a.trailing_zeros() + 64
263 } else {
264 self.b.trailing_zeros()
265 };
266 trail / 16
267 }
268
269 #[inline(always)]
271 pub fn len(self) -> u32 {
272 8 - self.missing_atoms()
273 }
274
275 pub fn is_empty(self) -> bool {
276 self.len() == 0
277 }
278
279 pub fn build_string(self) -> String {
283 let repo = lock_global_scope_repo();
284 repo.to_string(self)
285 }
286
287 pub fn is_prefix_of(self, s: Scope) -> bool {
311 let pref_missing = self.missing_atoms();
312
313 let mask: (u64, u64) = if pref_missing == 8 {
315 (0, 0)
316 } else if pref_missing == 4 {
317 (u64::MAX, 0)
318 } else if pref_missing > 4 {
319 (u64::MAX << ((pref_missing - 4) * 16), 0)
320 } else {
321 (u64::MAX, u64::MAX << (pref_missing * 16))
322 };
323
324 let ax = (self.a ^ s.a) & mask.0;
326 let bx = (self.b ^ s.b) & mask.1;
327 ax == 0 && bx == 0
331 }
332}
333
334impl FromStr for Scope {
335 type Err = ParseScopeError;
336
337 fn from_str(s: &str) -> Result<Scope, ParseScopeError> {
338 Scope::new(s)
339 }
340}
341
342impl fmt::Display for Scope {
343 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344 let s = self.build_string();
345 write!(f, "{}", s)
346 }
347}
348
349impl fmt::Debug for Scope {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 let s = self.build_string();
352 write!(f, "<{}>", s)
353 }
354}
355
356impl Serialize for Scope {
357 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
358 where
359 S: Serializer,
360 {
361 let s = self.build_string();
362 serializer.serialize_str(&s)
363 }
364}
365
366impl<'de> Deserialize<'de> for Scope {
367 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
368 where
369 D: Deserializer<'de>,
370 {
371 struct ScopeVisitor;
372
373 impl Visitor<'_> for ScopeVisitor {
374 type Value = Scope;
375
376 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
377 formatter.write_str("a string")
378 }
379
380 fn visit_str<E>(self, v: &str) -> Result<Scope, E>
381 where
382 E: Error,
383 {
384 Scope::new(v).map_err(|e| Error::custom(format!("Invalid scope: {:?}", e)))
385 }
386 }
387
388 deserializer.deserialize_str(ScopeVisitor)
389 }
390}
391
392#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
395pub struct MatchPower(pub f64);
396
397impl Eq for MatchPower {}
398
399#[allow(clippy::derive_ord_xor_partial_ord)] impl Ord for MatchPower {
401 fn cmp(&self, other: &Self) -> Ordering {
402 self.partial_cmp(other).unwrap()
403 }
404}
405
406impl ScopeStack {
407 pub fn owned_heap_bytes(&self) -> Option<usize> {
411 let bytes = self
412 .scopes
413 .capacity()
414 .checked_mul(mem::size_of::<Scope>())?
415 .checked_add(
416 self.clear_stack
417 .capacity()
418 .checked_mul(mem::size_of::<Vec<Scope>>())?,
419 )?;
420 self.clear_stack.iter().try_fold(bytes, |bytes, scopes| {
421 bytes.checked_add(scopes.capacity().checked_mul(mem::size_of::<Scope>())?)
422 })
423 }
424
425 pub fn new() -> ScopeStack {
426 ScopeStack {
427 clear_stack: Vec::new(),
428 scopes: Vec::new(),
429 }
430 }
431
432 pub fn from_vec(v: Vec<Scope>) -> ScopeStack {
435 ScopeStack {
436 clear_stack: Vec::new(),
437 scopes: v,
438 }
439 }
440
441 #[inline]
442 pub fn push(&mut self, s: Scope) {
443 self.scopes.push(s);
444 }
445
446 #[inline]
447 pub fn pop(&mut self) {
448 self.scopes.pop();
449 }
450
451 pub fn apply(&mut self, op: &ScopeStackOp) -> Result<(), ScopeError> {
455 self.apply_with_hook(op, |_, _| {})
456 }
457
458 #[inline]
466 pub fn apply_with_hook<F>(&mut self, op: &ScopeStackOp, mut hook: F) -> Result<(), ScopeError>
467 where
468 F: FnMut(BasicScopeStackOp, &[Scope]),
469 {
470 match *op {
471 ScopeStackOp::Push(scope) => {
472 self.scopes.push(scope);
473 hook(BasicScopeStackOp::Push(scope), self.as_slice());
474 }
475 ScopeStackOp::Pop(count) => {
476 for _ in 0..count {
477 self.scopes.pop();
478 hook(BasicScopeStackOp::Pop, self.as_slice());
479 }
480 }
481 ScopeStackOp::Clear(amount) => {
482 let cleared = match amount {
483 ClearAmount::TopN(n) => {
484 let to_leave = self.scopes.len() - min(n, self.scopes.len());
486 self.scopes.split_off(to_leave)
487 }
488 ClearAmount::All => {
489 let mut cleared = Vec::new();
490 mem::swap(&mut cleared, &mut self.scopes);
491 cleared
492 }
493 };
494 let clear_amount = cleared.len();
495 self.clear_stack.push(cleared);
496 for _ in 0..clear_amount {
497 hook(BasicScopeStackOp::Pop, self.as_slice());
498 }
499 }
500 ScopeStackOp::Restore => match self.clear_stack.pop() {
501 Some(ref mut to_push) => {
502 for s in to_push {
503 self.scopes.push(*s);
504 hook(BasicScopeStackOp::Push(*s), self.as_slice());
505 }
506 }
507 None => return Err(ScopeError::NoClearedScopesToRestore),
508 },
509 ScopeStackOp::Noop => (),
510 }
511
512 Ok(())
513 }
514
515 pub fn debug_print(&self, repo: &ScopeRepository) {
518 for s in &self.scopes {
519 print!("{} ", repo.to_string(*s));
520 }
521 println!();
522 }
523
524 pub fn bottom_n(&self, n: usize) -> &[Scope] {
528 &self.scopes[0..n]
529 }
530
531 #[inline]
533 pub fn as_slice(&self) -> &[Scope] {
534 &self.scopes[..]
535 }
536
537 #[inline]
539 pub fn len(&self) -> usize {
540 self.scopes.len()
541 }
542
543 #[inline]
544 pub fn is_empty(&self) -> bool {
545 self.len() == 0
546 }
547
548 pub fn does_match(&self, stack: &[Scope]) -> Option<MatchPower> {
570 let mut sel_index: usize = 0;
571 let mut score: f64 = 0.0;
572 for (i, scope) in stack.iter().enumerate() {
573 let sel_scope = self.scopes[sel_index];
574 if sel_scope.is_prefix_of(*scope) {
575 let len = sel_scope.len();
576 score += f64::from(len) * f64::from(ATOM_LEN_BITS * (i as u16)).exp2();
578 sel_index += 1;
579 if sel_index >= self.scopes.len() {
580 return Some(MatchPower(score));
581 }
582 }
583 }
584 None
585 }
586}
587
588impl FromStr for ScopeStack {
589 type Err = ParseScopeError;
590
591 fn from_str(s: &str) -> Result<ScopeStack, ParseScopeError> {
593 let mut scopes = Vec::new();
594 for name in s.split_whitespace() {
595 scopes.push(Scope::from_str(name)?)
596 }
597 Ok(ScopeStack::from_vec(scopes))
598 }
599}
600
601impl fmt::Display for ScopeStack {
602 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603 for s in &self.scopes {
604 write!(f, "{} ", s)?;
605 }
606 Ok(())
607 }
608}
609
610#[cfg(test)]
611mod tests {
612 use super::*;
613
614 #[test]
615 fn owned_heap_preserves_nested_clears_through_clone() {
616 let source = Scope::new("source.test").unwrap();
617 let outer = Scope::new("meta.outer.test").unwrap();
618 let embedded = Scope::new("source.embedded.test").unwrap();
619 let string = Scope::new("string.quoted.test").unwrap();
620 let mut stack = ScopeStack::from_vec(vec![source, outer]);
621 stack.apply(&ScopeStackOp::Clear(ClearAmount::All)).unwrap();
622 assert!(stack.scopes.is_empty());
623 assert!(stack.owned_heap_bytes().unwrap() >= 2 * mem::size_of::<Scope>());
624 stack.push(embedded);
625 stack.push(string);
626 stack
627 .apply(&ScopeStackOp::Clear(ClearAmount::TopN(1)))
628 .unwrap();
629 let mut restored = stack.clone();
630 assert_eq!(restored, stack);
631 assert_eq!(restored.clear_stack.len(), 2);
632 let minimum = 4 * mem::size_of::<Scope>() + 2 * mem::size_of::<Vec<Scope>>();
633 assert!(restored.owned_heap_bytes().unwrap() >= minimum);
634 for operation in [
635 ScopeStackOp::Restore,
636 ScopeStackOp::Pop(2),
637 ScopeStackOp::Restore,
638 ] {
639 stack.apply(&operation).unwrap();
640 restored.apply(&operation).unwrap();
641 assert_eq!(restored, stack);
642 }
643 assert_eq!(restored.scopes, vec![source, outer]);
644 assert!(restored.clear_stack.is_empty());
645 assert_eq!(
646 restored.owned_heap_bytes(),
647 Some(
648 restored.scopes.capacity() * mem::size_of::<Scope>()
649 + restored.clear_stack.capacity() * mem::size_of::<Vec<Scope>>()
650 )
651 );
652 }
653
654 #[test]
655 fn misc() {
656 }
662
663 #[test]
664 fn repo_works() {
665 let mut repo = ScopeRepository::new();
666 assert_eq!(
667 repo.build("source.php").unwrap(),
668 repo.build("source.php").unwrap()
669 );
670 assert_eq!(
671 repo.build("source.php.wow.hi.bob.troll.clock.5").unwrap(),
672 repo.build("source.php.wow.hi.bob.troll.clock.5").unwrap()
673 );
674 assert_eq!(repo.build("").unwrap(), repo.build("").unwrap());
675 let s1 = repo.build("").unwrap();
676 assert_eq!(repo.to_string(s1), "");
677 let s2 = repo.build("source.php.wow").unwrap();
678 assert_eq!(repo.to_string(s2), "source.php.wow");
679 assert!(repo.build("source.php").unwrap() != repo.build("source.perl").unwrap());
680 assert!(repo.build("source.php").unwrap() != repo.build("source.php.wagon").unwrap());
681 assert_eq!(
682 repo.build("comment.line.").unwrap(),
683 repo.build("comment.line").unwrap()
684 );
685 }
686
687 #[test]
688 fn global_repo_works() {
689 use std::str::FromStr;
690 assert_eq!(
691 Scope::new("source.php").unwrap(),
692 Scope::new("source.php").unwrap()
693 );
694 assert!(Scope::from_str("1.2.3.4.5.6.7.8").is_ok());
695 assert!(Scope::from_str("1.2.3.4.5.6.7.8.9").is_err());
696 }
697
698 #[test]
699 fn prefixes_work() {
700 assert!(Scope::new("1.2.3.4.5.6.7.8")
701 .unwrap()
702 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
703 assert!(Scope::new("1.2.3.4.5.6")
704 .unwrap()
705 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
706 assert!(Scope::new("1.2.3.4")
707 .unwrap()
708 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
709 assert!(!Scope::new("1.2.3.4.5.6.a")
710 .unwrap()
711 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
712 assert!(!Scope::new("1.2.a.4.5.6.7")
713 .unwrap()
714 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
715 assert!(!Scope::new("1.2.a.4.5.6.7")
716 .unwrap()
717 .is_prefix_of(Scope::new("1.2.3.4.5").unwrap()));
718 assert!(!Scope::new("1.2.a")
719 .unwrap()
720 .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
721 }
722
723 #[test]
724 fn matching_works() {
725 use std::str::FromStr;
726 assert_eq!(
727 ScopeStack::from_str("string")
728 .unwrap()
729 .does_match(ScopeStack::from_str("string.quoted").unwrap().as_slice()),
730 Some(MatchPower(0o1u64 as f64))
731 );
732 assert_eq!(
733 ScopeStack::from_str("source")
734 .unwrap()
735 .does_match(ScopeStack::from_str("string.quoted").unwrap().as_slice()),
736 None
737 );
738 assert_eq!(
739 ScopeStack::from_str("a.b e.f")
740 .unwrap()
741 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
742 Some(MatchPower(0o202u64 as f64))
743 );
744 assert_eq!(
745 ScopeStack::from_str("c e.f")
746 .unwrap()
747 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
748 Some(MatchPower(0o210u64 as f64))
749 );
750 assert_eq!(
751 ScopeStack::from_str("c.d e.f")
752 .unwrap()
753 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
754 Some(MatchPower(0o220u64 as f64))
755 );
756 assert_eq!(
757 ScopeStack::from_str("a.b c e.f")
758 .unwrap()
759 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
760 Some(MatchPower(0o212u64 as f64))
761 );
762 assert_eq!(
763 ScopeStack::from_str("a c.d")
764 .unwrap()
765 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
766 Some(MatchPower(0o021u64 as f64))
767 );
768 assert_eq!(
769 ScopeStack::from_str("a c.d.e")
770 .unwrap()
771 .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
772 None
773 );
774 }
775}