syntect_no_panic/parsing/
scope.rs

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