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