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