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