1 // see DESIGN.md
2 use std::collections::HashMap;
3 use std::u16;
4 use std::sync::Mutex;
5 use std::fmt;
6 use std::str::FromStr;
7 use std::u64;
8 use std::cmp::{Ordering, min};
9 use std::mem;
10 
11 use serde::{Deserialize, Deserializer, Serialize, Serializer};
12 use serde::de::{Error, Visitor};
13 
14 /// Multiplier on the power of 2 for MatchPower. This is only useful if you compute your own
15 /// [`MatchPower`] scores
16 ///
17 /// [`MatchPower`]: struct.MatchPower.html
18 pub const ATOM_LEN_BITS: u16 = 3;
19 
20 lazy_static! {
21     /// The global scope repo, exposed in case you want to minimize locking and unlocking.
22     ///
23     /// Ths shouldn't be necessary for you to use. See the [`ScopeRepository`] docs.
24     ///
25     /// [`ScopeRepository`]: struct.ScopeRepository.html
26     pub static ref SCOPE_REPO: Mutex<ScopeRepository> = Mutex::new(ScopeRepository::new());
27 }
28 
29 /// A hierarchy of atoms with semi-standardized names used to accord semantic information to a
30 /// specific piece of text.
31 ///
32 /// These are generally written with the atoms separated by dots, and - by convention - atoms are
33 /// all lowercase alphanumeric.
34 ///
35 /// Example scopes: `text.plain`, `punctuation.definition.string.begin.ruby`,
36 /// `meta.function.parameters.rust`
37 ///
38 /// `syntect` uses an optimized format for storing these that allows super fast comparison and
39 /// determining if one scope is a prefix of another. It also always takes 16 bytes of space. It
40 /// accomplishes this by using a global repository to store string values and using bit-packed 16
41 /// bit numbers to represent and compare atoms. Like "atoms" or "symbols" in other languages. This
42 /// means that while comparing and prefix are fast, extracting a string is relatively slower but
43 /// ideally should be very rare.
44 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Default, Hash)]
45 pub struct Scope {
46     a: u64,
47     b: u64,
48 }
49 
50 /// Not all strings are valid scopes
51 #[derive(Debug)]
52 pub enum ParseScopeError {
53     /// Due to a limitation of the current optimized internal representation
54     /// scopes can be at most 8 atoms long
55     TooLong,
56     /// The internal representation uses 16 bits per atom, so if all scopes ever
57     /// used by the program have more than 2^16-2 atoms, things break
58     TooManyAtoms,
59 }
60 
61 /// The structure used to keep track of the mapping between scope atom numbers and their string
62 /// names
63 ///
64 /// It is only exposed in case you want to lock [`SCOPE_REPO`] and then allocate a bunch of scopes
65 /// at once without thrashing the lock. In general, you should just use [`Scope::new()`].
66 ///
67 /// Only [`Scope`]s created by the same repository have valid comparison results.
68 ///
69 /// [`SCOPE_REPO`]: struct.SCOPE_REPO.html
70 /// [`Scope::new()`]: struct.Scope.html#method.new
71 /// [`Scope`]: struct.Scope.html
72 #[derive(Debug)]
73 pub struct ScopeRepository {
74     atoms: Vec<String>,
75     atom_index_map: HashMap<String, usize>,
76 }
77 
78 /// A stack/sequence of scopes for representing hierarchies for a given token of text
79 ///
80 /// This is also used within [`ScopeSelectors`].
81 ///
82 /// In Sublime Text, the scope stack at a given point can be seen by pressing `ctrl+shift+p`. Also
83 /// see [the TextMate docs](https://manual.macromates.com/en/scope_selectors).
84 ///
85 /// Example for a JS string inside a script tag in a Rails `ERB` file:
86 /// `text.html.ruby text.html.basic source.js.embedded.html string.quoted.double.js`
87 ///
88 /// [`ScopeSelectors`]: ../highlighting/struct.ScopeSelectors.html
89 #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
90 pub struct ScopeStack {
91     clear_stack: Vec<Vec<Scope>>,
92     pub scopes: Vec<Scope>,
93 }
94 
95 #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
96 pub enum ClearAmount {
97     TopN(usize),
98     All,
99 }
100 
101 /// A change to a scope stack
102 ///
103 /// Generally, `Noop` is only used internally and you won't need to worry about getting one back
104 /// from calling a public function.
105 ///
106 /// The change from a `ScopeStackOp` can be applied via [`ScopeStack::apply`].
107 ///
108 /// [`ScopeStack::apply`]: struct.ScopeStack.html#method.apply
109 #[derive(Debug, Clone, PartialEq, Eq)]
110 pub enum ScopeStackOp {
111     Push(Scope),
112     Pop(usize),
113     /// Used for the `clear_scopes` feature
114     Clear(ClearAmount),
115     /// Restores cleared scopes
116     Restore,
117     Noop,
118 }
119 
120 /// Used for [`ScopeStack::apply_with_hook`]
121 ///
122 /// [`ScopeStack::apply_with_hook`]: struct.ScopeStack.html#method.apply_with_hook
123 #[derive(Debug, Clone, PartialEq, Eq)]
124 pub enum BasicScopeStackOp {
125     Push(Scope),
126     Pop,
127 }
128 
pack_as_u16s(atoms: &[usize]) -> Result<Scope, ParseScopeError>129 fn pack_as_u16s(atoms: &[usize]) -> Result<Scope, ParseScopeError> {
130     let mut res = Scope { a: 0, b: 0 };
131 
132     for (i, &n) in atoms.iter().enumerate() {
133         if n >= (u16::MAX as usize) - 2 {
134             return Err(ParseScopeError::TooManyAtoms);
135         }
136         let small = (n + 1) as u64; // +1 since we reserve 0 for unused
137 
138         if i < 4 {
139             let shift = (3 - i) * 16;
140             res.a |= small << shift;
141         } else {
142             let shift = (7 - i) * 16;
143             res.b |= small << shift;
144         }
145     }
146     Ok(res)
147 }
148 
149 impl ScopeRepository {
new() -> ScopeRepository150     fn new() -> ScopeRepository {
151         ScopeRepository {
152             atoms: Vec::new(),
153             atom_index_map: HashMap::new(),
154         }
155     }
156 
build(&mut self, s: &str) -> Result<Scope, ParseScopeError>157     pub fn build(&mut self, s: &str) -> Result<Scope, ParseScopeError> {
158         if s.is_empty() {
159             return Ok(Scope { a: 0, b: 0 });
160         }
161         let parts: Vec<usize> = s.trim_end_matches('.').split('.').map(|a| self.atom_to_index(a)).collect();
162         if parts.len() > 8 {
163             return Err(ParseScopeError::TooManyAtoms);
164         }
165         pack_as_u16s(&parts[..])
166     }
167 
to_string(&self, scope: Scope) -> String168     pub fn to_string(&self, scope: Scope) -> String {
169         let mut s = String::new();
170         for i in 0..8 {
171             let atom_number = scope.atom_at(i);
172             // println!("atom {} of {:x}-{:x} = {:x}",
173             //     i, scope.a, scope.b, atom_number);
174             if atom_number == 0 {
175                 break;
176             }
177             if i != 0 {
178                 s.push_str(".");
179             }
180             s.push_str(self.atom_str(atom_number));
181         }
182         s
183     }
184 
atom_to_index(&mut self, atom: &str) -> usize185     fn atom_to_index(&mut self, atom: &str) -> usize {
186         if let Some(index) = self.atom_index_map.get(atom) {
187             return *index;
188         }
189 
190         self.atoms.push(atom.to_owned());
191         let index = self.atoms.len() - 1;
192         self.atom_index_map.insert(atom.to_owned(), index);
193 
194         index
195     }
196 
197     /// Return the string for an atom number returned by [`Scope::atom_at`]
198     ///
199     /// [`Scope::atom_at`]: struct.Scope.html#method.atom_at
atom_str(&self, atom_number: u16) -> &str200     pub fn atom_str(&self, atom_number: u16) -> &str {
201         &self.atoms[(atom_number - 1) as usize]
202     }
203 }
204 
205 impl Scope {
206     /// Parses a `Scope` from a series of atoms separated by dot (`.`) characters
207     ///
208     /// Example: `Scope::new("meta.rails.controller")`
new(s: &str) -> Result<Scope, ParseScopeError>209     pub fn new(s: &str) -> Result<Scope, ParseScopeError> {
210         let mut repo = SCOPE_REPO.lock().unwrap();
211         repo.build(s.trim())
212     }
213 
214     /// Gets the atom number at a given index.
215     ///
216     /// I can't think of any reason you'd find this useful. It is used internally for turning a
217     /// scope back into a string.
atom_at(self, index: usize) -> u16218     pub fn atom_at(self, index: usize) -> u16 {
219         let shifted = if index < 4 {
220             self.a >> ((3 - index) * 16)
221         } else if index < 8 {
222             self.b >> ((7 - index) * 16)
223         } else {
224             panic!("atom index out of bounds {:?}", index);
225         };
226         (shifted & 0xFFFF) as u16
227     }
228 
229     #[inline]
missing_atoms(self) -> u32230     fn missing_atoms(self) -> u32 {
231         let trail = if self.b == 0 {
232             self.a.trailing_zeros() + 64
233         } else {
234             self.b.trailing_zeros()
235         };
236         trail / 16
237     }
238 
239     /// Returns the number of atoms in the scope
240     #[inline(always)]
len(self) -> u32241     pub fn len(self) -> u32 {
242         8 - self.missing_atoms()
243     }
244 
is_empty(&self) -> bool245     pub fn is_empty(&self) -> bool {
246         self.len() == 0
247     }
248 
249     /// Returns a string representation of this scope
250     ///
251     /// This requires locking a global repo and shouldn't be done frequently.
build_string(self) -> String252     pub fn build_string(self) -> String {
253         let repo = SCOPE_REPO.lock().unwrap();
254         repo.to_string(self)
255     }
256 
257     /// Tests if this scope is a prefix of another scope. Note that the empty scope is always a
258     /// prefix.
259     ///
260     /// This operation uses bitwise operations and is very fast
261     /// # Examples
262     ///
263     /// ```
264     /// use syntect::parsing::Scope;
265     /// assert!( Scope::new("string").unwrap()
266     ///         .is_prefix_of(Scope::new("string.quoted").unwrap()));
267     /// assert!( Scope::new("string.quoted").unwrap()
268     ///         .is_prefix_of(Scope::new("string.quoted").unwrap()));
269     /// assert!( Scope::new("").unwrap()
270     ///         .is_prefix_of(Scope::new("meta.rails.controller").unwrap()));
271     /// assert!(!Scope::new("source.php").unwrap()
272     ///         .is_prefix_of(Scope::new("source").unwrap()));
273     /// assert!(!Scope::new("source.php").unwrap()
274     ///         .is_prefix_of(Scope::new("source.ruby").unwrap()));
275     /// assert!(!Scope::new("meta.php").unwrap()
276     ///         .is_prefix_of(Scope::new("source.php").unwrap()));
277     /// assert!(!Scope::new("meta.php").unwrap()
278     ///         .is_prefix_of(Scope::new("source.php.wow").unwrap()));
279     /// ```
is_prefix_of(self, s: Scope) -> bool280     pub fn is_prefix_of(self, s: Scope) -> bool {
281         let pref_missing = self.missing_atoms();
282 
283         // TODO: test optimization - use checked shl and then mult carry flag as int by -1
284         let mask: (u64, u64) = if pref_missing == 8 {
285             (0, 0)
286         } else if pref_missing == 4 {
287             (u64::MAX, 0)
288         } else if pref_missing > 4 {
289             (u64::MAX << ((pref_missing - 4) * 16), 0)
290         } else {
291             (u64::MAX, u64::MAX << (pref_missing * 16))
292         };
293 
294         // xor to find the difference
295         let ax = (self.a ^ s.a) & mask.0;
296         let bx = (self.b ^ s.b) & mask.1;
297         // println!("{:x}-{:x} is_pref {:x}-{:x}: missing {} mask {:x}-{:x} xor {:x}-{:x}",
298         //     self.a, self.b, s.a, s.b, pref_missing, mask.0, mask.1, ax, bx);
299 
300         ax == 0 && bx == 0
301     }
302 }
303 
304 impl FromStr for Scope {
305     type Err = ParseScopeError;
306 
from_str(s: &str) -> Result<Scope, ParseScopeError>307     fn from_str(s: &str) -> Result<Scope, ParseScopeError> {
308         Scope::new(s)
309     }
310 }
311 
312 impl fmt::Display for Scope {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result313     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314         let s = self.build_string();
315         write!(f, "{}", s)
316     }
317 }
318 
319 impl fmt::Debug for Scope {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result320     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321         let s = self.build_string();
322         write!(f, "<{}>", s)
323     }
324 }
325 
326 impl Serialize for Scope {
serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer327     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
328         let s = self.build_string();
329         serializer.serialize_str(&s)
330     }
331 }
332 
333 impl<'de> Deserialize<'de> for Scope {
deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>334     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
335 
336         struct ScopeVisitor;
337 
338         impl<'de> Visitor<'de> for ScopeVisitor {
339             type Value = Scope;
340 
341             fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
342                 formatter.write_str("a string")
343             }
344 
345             fn visit_str<E>(self, v: &str) -> Result<Scope, E> where E: Error {
346                 Scope::new(v).map_err(|e| Error::custom(format!("Invalid scope: {:?}", e)))
347             }
348         }
349 
350         deserializer.deserialize_str(ScopeVisitor)
351     }
352 }
353 
354 /// Wrapper to get around the fact Rust `f64` doesn't implement `Ord` and there is no non-NaN
355 /// float type
356 #[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
357 pub struct MatchPower(pub f64);
358 
359 impl Eq for MatchPower {}
360 
361 impl Ord for MatchPower {
cmp(&self, other: &Self) -> Ordering362     fn cmp(&self, other: &Self) -> Ordering {
363         self.partial_cmp(other).unwrap()
364     }
365 }
366 
367 impl ScopeStack {
new() -> ScopeStack368     pub fn new() -> ScopeStack {
369         ScopeStack {
370             clear_stack: Vec::new(),
371             scopes: Vec::new()
372         }
373     }
374 
375     /// Note: creating a ScopeStack with this doesn't contain information
376     /// on what to do when `clear_scopes` contexts end.
from_vec(v: Vec<Scope>) -> ScopeStack377     pub fn from_vec(v: Vec<Scope>) -> ScopeStack {
378         ScopeStack {
379             clear_stack: Vec::new(),
380             scopes: v
381         }
382     }
383 
384     #[inline]
push(&mut self, s: Scope)385     pub fn push(&mut self, s: Scope) {
386         self.scopes.push(s);
387     }
388 
389     #[inline]
pop(&mut self)390     pub fn pop(&mut self) {
391         self.scopes.pop();
392     }
393 
394     /// Modifies this stack according to the operation given
395     ///
396     /// Use this to create a stack from a `Vec` of changes given by the parser.
apply(&mut self, op: &ScopeStackOp)397     pub fn apply(&mut self, op: &ScopeStackOp) {
398         self.apply_with_hook(op, |_,_|{})
399     }
400 
401     /// Modifies this stack according to the operation given and calls the hook for each basic operation.
402     ///
403     /// Like [`apply`] but calls `hook` for every basic modification (as defined by
404     /// [`BasicScopeStackOp`]). Use this to do things only when the scope stack changes.
405     ///
406     /// [`apply`]: #method.apply
407     /// [`BasicScopeStackOp`]: enum.BasicScopeStackOp.html
408     #[inline]
apply_with_hook<F>(&mut self, op: &ScopeStackOp, mut hook: F) where F: FnMut(BasicScopeStackOp, &[Scope])409     pub fn apply_with_hook<F>(&mut self, op: &ScopeStackOp, mut hook: F)
410         where F: FnMut(BasicScopeStackOp, &[Scope])
411     {
412         match *op {
413             ScopeStackOp::Push(scope) => {
414                 self.scopes.push(scope);
415                 hook(BasicScopeStackOp::Push(scope), self.as_slice());
416             }
417             ScopeStackOp::Pop(count) => {
418                 for _ in 0..count {
419                     self.scopes.pop();
420                     hook(BasicScopeStackOp::Pop, self.as_slice());
421                 }
422             }
423             ScopeStackOp::Clear(amount) => {
424                 let cleared = match amount {
425                     ClearAmount::TopN(n) => {
426                         // don't try to clear more scopes than are on the stack
427                         let to_leave = self.scopes.len() - min(n, self.scopes.len());
428                         self.scopes.split_off(to_leave)
429                     }
430                     ClearAmount::All => {
431                         let mut cleared = Vec::new();
432                         mem::swap(&mut cleared, &mut self.scopes);
433                         cleared
434                     }
435                 };
436                 let clear_amount = cleared.len();
437                 self.clear_stack.push(cleared);
438                 for _ in 0..clear_amount {
439                     hook(BasicScopeStackOp::Pop, self.as_slice());
440                 }
441             }
442             ScopeStackOp::Restore => {
443                 match self.clear_stack.pop() {
444                     Some(ref mut to_push) => {
445                         for s in to_push {
446                             self.scopes.push(*s);
447                             hook(BasicScopeStackOp::Push(*s), self.as_slice());
448                         }
449                     }
450                     None => panic!("tried to restore cleared scopes, but none were cleared"),
451                 }
452             }
453             ScopeStackOp::Noop => (),
454         }
455     }
456 
457     /// Prints out each scope in the stack separated by spaces
458     /// and then a newline. Top of the stack at the end.
debug_print(&self, repo: &ScopeRepository)459     pub fn debug_print(&self, repo: &ScopeRepository) {
460         for s in &self.scopes {
461             print!("{} ", repo.to_string(*s));
462         }
463         println!();
464     }
465 
466     /// Returns the bottom `n` elements of the stack.
467     ///
468     /// Equivalent to `&scopes[0..n]` on a `Vec`
bottom_n(&self, n: usize) -> &[Scope]469     pub fn bottom_n(&self, n: usize) -> &[Scope] {
470         &self.scopes[0..n]
471     }
472 
473     /// Return a slice of the scopes in this stack
474     #[inline]
as_slice(&self) -> &[Scope]475     pub fn as_slice(&self) -> &[Scope] {
476         &self.scopes[..]
477     }
478 
479     /// Return the height/length of this stack
480     #[inline]
len(&self) -> usize481     pub fn len(&self) -> usize {
482         self.scopes.len()
483     }
484 
485     #[inline]
is_empty(&self) -> bool486     pub fn is_empty(&self) -> bool {
487         self.len() == 0
488     }
489 
490     /// Checks if this stack as a selector matches the given stack, returning the match score if so
491     ///
492     /// Higher match scores indicate stronger matches. Scores are ordered according to the rules
493     /// found at [https://manual.macromates.com/en/scope_selectors](https://manual.macromates.com/en/scope_selectors)
494     ///
495     /// It accomplishes this ordering through some floating point math ensuring deeper and longer
496     /// matches matter. Unfortunately it is only guaranteed to return perfectly accurate results up
497     /// to stack depths of 17, but it should be reasonably good even afterwards. TextMate has the
498     /// exact same limitation, dunno about Sublime Text.
499     ///
500     /// # Examples
501     /// ```
502     /// use syntect::parsing::{ScopeStack, MatchPower};
503     /// use std::str::FromStr;
504     /// assert_eq!(ScopeStack::from_str("a.b c e.f").unwrap()
505     ///     .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
506     ///     Some(MatchPower(0o212u64 as f64)));
507     /// assert_eq!(ScopeStack::from_str("a c.d.e").unwrap()
508     ///     .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
509     ///     None);
510     /// ```
does_match(&self, stack: &[Scope]) -> Option<MatchPower>511     pub fn does_match(&self, stack: &[Scope]) -> Option<MatchPower> {
512         let mut sel_index: usize = 0;
513         let mut score: f64 = 0.0;
514         for (i, scope) in stack.iter().enumerate() {
515             let sel_scope = self.scopes[sel_index];
516             if sel_scope.is_prefix_of(*scope) {
517                 let len = sel_scope.len();
518                 // equivalent to score |= len << (ATOM_LEN_BITS*i) on a large unsigned
519                 score += f64::from(len) * f64::from(ATOM_LEN_BITS * (i as u16)).exp2();
520                 sel_index += 1;
521                 if sel_index >= self.scopes.len() {
522                     return Some(MatchPower(score));
523                 }
524             }
525         }
526         None
527     }
528 }
529 
530 impl FromStr for ScopeStack {
531     type Err = ParseScopeError;
532 
533     /// Parses a scope stack from a whitespace separated list of scopes.
from_str(s: &str) -> Result<ScopeStack, ParseScopeError>534     fn from_str(s: &str) -> Result<ScopeStack, ParseScopeError> {
535         let mut scopes = Vec::new();
536         for name in s.split_whitespace() {
537             scopes.push(Scope::from_str(name)?)
538         }
539         Ok(ScopeStack::from_vec(scopes))
540     }
541 }
542 
543 impl fmt::Display for ScopeStack {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result544     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545         for s in &self.scopes {
546             write!(f, "{} ", s)?;
547         }
548         Ok(())
549     }
550 }
551 
552 #[cfg(test)]
553 mod tests {
554     use super::*;
555 
556     #[test]
misc()557     fn misc() {
558         // use std::mem;
559         // use std::rc::{Rc};
560         // use scope::*;
561         // assert_eq!(8, mem::size_of::<Rc<Scope>>());
562         // assert_eq!(Scope::new("source.php"), Scope::new("source.php"));
563     }
564 
565     #[test]
repo_works()566     fn repo_works() {
567         let mut repo = ScopeRepository::new();
568         assert_eq!(repo.build("source.php").unwrap(),
569                    repo.build("source.php").unwrap());
570         assert_eq!(repo.build("source.php.wow.hi.bob.troll.clock.5").unwrap(),
571                    repo.build("source.php.wow.hi.bob.troll.clock.5").unwrap());
572         assert_eq!(repo.build("").unwrap(), repo.build("").unwrap());
573         let s1 = repo.build("").unwrap();
574         assert_eq!(repo.to_string(s1), "");
575         let s2 = repo.build("source.php.wow").unwrap();
576         assert_eq!(repo.to_string(s2), "source.php.wow");
577         assert!(repo.build("source.php").unwrap() != repo.build("source.perl").unwrap());
578         assert!(repo.build("source.php").unwrap() != repo.build("source.php.wagon").unwrap());
579         assert_eq!(repo.build("comment.line.").unwrap(),
580                    repo.build("comment.line").unwrap());
581     }
582 
583     #[test]
global_repo_works()584     fn global_repo_works() {
585         use std::str::FromStr;
586         assert_eq!(Scope::new("source.php").unwrap(),
587                    Scope::new("source.php").unwrap());
588         assert!(Scope::from_str("1.2.3.4.5.6.7.8").is_ok());
589         assert!(Scope::from_str("1.2.3.4.5.6.7.8.9").is_err());
590     }
591 
592     #[test]
prefixes_work()593     fn prefixes_work() {
594         assert!(Scope::new("1.2.3.4.5.6.7.8")
595             .unwrap()
596             .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
597         assert!(Scope::new("1.2.3.4.5.6")
598             .unwrap()
599             .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
600         assert!(Scope::new("1.2.3.4")
601             .unwrap()
602             .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
603         assert!(!Scope::new("1.2.3.4.5.6.a")
604             .unwrap()
605             .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
606         assert!(!Scope::new("1.2.a.4.5.6.7")
607             .unwrap()
608             .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
609         assert!(!Scope::new("1.2.a.4.5.6.7")
610             .unwrap()
611             .is_prefix_of(Scope::new("1.2.3.4.5").unwrap()));
612         assert!(!Scope::new("1.2.a")
613             .unwrap()
614             .is_prefix_of(Scope::new("1.2.3.4.5.6.7.8").unwrap()));
615     }
616 
617     #[test]
matching_works()618     fn matching_works() {
619         use std::str::FromStr;
620         assert_eq!(ScopeStack::from_str("string")
621                        .unwrap()
622                        .does_match(ScopeStack::from_str("string.quoted").unwrap().as_slice()),
623                    Some(MatchPower(0o1u64 as f64)));
624         assert_eq!(ScopeStack::from_str("source")
625                        .unwrap()
626                        .does_match(ScopeStack::from_str("string.quoted").unwrap().as_slice()),
627                    None);
628         assert_eq!(ScopeStack::from_str("a.b e.f")
629                        .unwrap()
630                        .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
631                    Some(MatchPower(0o202u64 as f64)));
632         assert_eq!(ScopeStack::from_str("c e.f")
633                        .unwrap()
634                        .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
635                    Some(MatchPower(0o210u64 as f64)));
636         assert_eq!(ScopeStack::from_str("c.d e.f")
637                        .unwrap()
638                        .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
639                    Some(MatchPower(0o220u64 as f64)));
640         assert_eq!(ScopeStack::from_str("a.b c e.f")
641                        .unwrap()
642                        .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
643                    Some(MatchPower(0o212u64 as f64)));
644         assert_eq!(ScopeStack::from_str("a c.d")
645                        .unwrap()
646                        .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
647                    Some(MatchPower(0o021u64 as f64)));
648         assert_eq!(ScopeStack::from_str("a c.d.e")
649                        .unwrap()
650                        .does_match(ScopeStack::from_str("a.b c.d e.f.g").unwrap().as_slice()),
651                    None);
652     }
653 }
654