1#![allow(clippy::mutable_key_type)]
12
13use super::regex::{Region, SearchScratch};
14use super::scope::*;
15use super::syntax_definition::*;
16use crate::parsing::syntax_definition::ContextId;
17use crate::parsing::syntax_set::{SyntaxReference, SyntaxSet};
18use fnv::FnvHasher;
19use std::collections::HashMap;
20use std::hash::BuildHasherDefault;
21
22struct LineSearch {
23 cache: SearchCache,
24 region: Region,
25 scratch: SearchScratch,
26 ascii_bytes: u128,
27}
28
29#[derive(Debug, thiserror::Error)]
31#[non_exhaustive]
32pub enum ParsingError {
33 #[error("Somehow main context was popped from the stack")]
34 MissingMainContext,
35 #[error("Missing context with ID '{0:?}'")]
38 MissingContext(ContextId),
39 #[error("Bad index to match_at: {0}")]
40 BadMatchIndex(usize),
41 #[error("Tried to use a ContextReference that has not bee resolved yet: {0:?}")]
42 UnresolvedContextReference(ContextReference),
43}
44
45#[derive(Debug, Clone, Eq, PartialEq)]
65pub struct ParseState {
66 stack: Vec<StateLevel>,
67 first_line: bool,
68 proto_starts: Vec<usize>,
71}
72
73#[derive(Debug, Clone, Eq, PartialEq)]
74struct StateLevel {
75 context: ContextId,
76 prototypes: Vec<ContextId>,
77 captures: Option<(Region, String)>,
78}
79
80#[derive(Debug)]
81struct RegexMatch<'a> {
82 regions: Region,
83 context: &'a Context,
84 pat_index: usize,
85 from_with_prototype: bool,
86 would_loop: bool,
87}
88
89type SearchCache = HashMap<*const MatchPattern, Option<Region>, BuildHasherDefault<FnvHasher>>;
91
92impl ParseState {
185 pub fn owned_heap_bytes(&self) -> Option<usize> {
189 let bytes = self
190 .stack
191 .capacity()
192 .checked_mul(std::mem::size_of::<StateLevel>())?
193 .checked_add(
194 self.proto_starts
195 .capacity()
196 .checked_mul(std::mem::size_of::<usize>())?,
197 )?;
198 self.stack.iter().try_fold(bytes, |bytes, level| {
199 let bytes = bytes.checked_add(
200 level
201 .prototypes
202 .capacity()
203 .checked_mul(std::mem::size_of::<ContextId>())?,
204 )?;
205 match &level.captures {
206 Some((region, text)) => bytes
207 .checked_add(text.capacity())?
208 .checked_add(region.owned_heap_bytes()?),
209 None => Some(bytes),
210 }
211 })
212 }
213
214 pub fn new(syntax: &SyntaxReference) -> ParseState {
217 let start_state = StateLevel {
218 context: syntax.context_ids()["__start"],
219 prototypes: Vec::new(),
220 captures: None,
221 };
222 ParseState {
223 stack: vec![start_state],
224 first_line: true,
225 proto_starts: Vec::new(),
226 }
227 }
228
229 pub fn parse_line(
249 &mut self,
250 line: &str,
251 syntax_set: &SyntaxSet,
252 ) -> Result<Vec<(usize, ScopeStackOp)>, ParsingError> {
253 if self.stack.is_empty() {
254 return Err(ParsingError::MissingMainContext);
255 }
256 let mut match_start = 0;
257 let mut res = Vec::new();
258
259 if self.first_line {
260 let cur_level = &self.stack[self.stack.len() - 1];
261 let context = syntax_set.get_context(&cur_level.context)?;
262 if !context.meta_content_scope.is_empty() {
263 res.push((0, ScopeStackOp::Push(context.meta_content_scope[0])));
264 }
265 self.first_line = false;
266 }
267
268 let fnv = BuildHasherDefault::<FnvHasher>::default();
269 let mut search = LineSearch {
270 cache: HashMap::with_capacity_and_hasher(128, fnv),
271 region: Region::new(),
272 scratch: SearchScratch::default(),
273 ascii_bytes: line
274 .bytes()
275 .filter(u8::is_ascii)
276 .fold(0, |bytes, byte| bytes | (1u128 << byte)),
277 };
278 let mut non_consuming_push_at = (0, 0);
280
281 while self.parse_next_token(
282 line,
283 syntax_set,
284 &mut match_start,
285 &mut search,
286 &mut non_consuming_push_at,
287 &mut res,
288 )? {}
289
290 Ok(res)
291 }
292
293 #[allow(clippy::too_many_arguments)]
294 fn parse_next_token(
295 &mut self,
296 line: &str,
297 syntax_set: &SyntaxSet,
298 start: &mut usize,
299 search: &mut LineSearch,
300 non_consuming_push_at: &mut (usize, usize),
301 ops: &mut Vec<(usize, ScopeStackOp)>,
302 ) -> Result<bool, ParsingError> {
303 let check_pop_loop = {
304 let (pos, stack_depth) = *non_consuming_push_at;
305 pos == *start && stack_depth == self.stack.len()
306 };
307
308 while self
310 .proto_starts
311 .last()
312 .map(|start| *start >= self.stack.len())
313 .unwrap_or(false)
314 {
315 self.proto_starts.pop();
316 }
317
318 let best_match = self.find_best_match(line, *start, syntax_set, search, check_pop_loop)?;
319
320 if let Some(reg_match) = best_match {
321 if reg_match.would_loop {
322 if let Some((i, _)) = line[*start..].char_indices().nth(1) {
336 *start += i;
337 return Ok(true);
338 } else {
339 return Ok(false);
342 }
343 }
344
345 let match_end = reg_match.regions.pos(0).unwrap().1;
346
347 let consuming = match_end > *start;
348 if !consuming {
349 let context = reg_match.context;
354 let match_pattern = context.match_at(reg_match.pat_index)?;
355 if let MatchOperation::Push(_) = match_pattern.operation {
356 *non_consuming_push_at = (match_end, self.stack.len() + 1);
357 }
358 }
359
360 *start = match_end;
361
362 if reg_match.from_with_prototype {
364 self.proto_starts.push(self.stack.len());
366 }
367
368 let level_context = {
369 let id = &self.stack[self.stack.len() - 1].context;
370 syntax_set.get_context(id)?
371 };
372 self.exec_pattern(line, ®_match, level_context, syntax_set, ops)?;
373
374 Ok(true)
375 } else {
376 Ok(false)
377 }
378 }
379
380 fn find_best_match<'a>(
381 &self,
382 line: &str,
383 start: usize,
384 syntax_set: &'a SyntaxSet,
385 search: &mut LineSearch,
386 check_pop_loop: bool,
387 ) -> Result<Option<RegexMatch<'a>>, ParsingError> {
388 let cur_level = &self.stack[self.stack.len() - 1];
389 let context = syntax_set.get_context(&cur_level.context)?;
390 let prototype = if let Some(ref p) = context.prototype {
391 Some(p)
392 } else {
393 None
394 };
395
396 let context_chain = {
398 let proto_start = self.proto_starts.last().cloned().unwrap_or(0);
399 let with_prototypes = self.stack[proto_start..].iter().flat_map(|lvl| {
401 lvl.prototypes
402 .iter()
403 .map(move |ctx| (true, ctx, lvl.captures.as_ref()))
404 });
405 let cur_prototype = prototype.into_iter().map(|ctx| (false, ctx, None));
406 let cur_context =
407 Some((false, &cur_level.context, cur_level.captures.as_ref())).into_iter();
408 with_prototypes.chain(cur_prototype).chain(cur_context)
409 };
410
411 let mut min_start = usize::MAX;
415 let mut best_match: Option<RegexMatch<'_>> = None;
416 let mut pop_would_loop = false;
417
418 for (from_with_proto, ctx, captures) in context_chain {
419 for (pat_context, pat_index) in context_iter(syntax_set, syntax_set.get_context(ctx)?) {
420 let match_pat = pat_context.match_at(pat_index)?;
421
422 if let Some(match_region) = self.search(line, start, match_pat, captures, search) {
423 let (match_start, match_end) = match_region.pos(0).unwrap();
424
425 if match_start < min_start || (match_start == min_start && pop_would_loop) {
428 min_start = match_start;
435
436 let consuming = match_end > start;
437 pop_would_loop = check_pop_loop
438 && !consuming
439 && matches!(match_pat.operation, MatchOperation::Pop);
440
441 let push_too_deep = matches!(match_pat.operation, MatchOperation::Push(_))
442 && self.stack.len() >= 100;
443
444 if push_too_deep {
445 return Ok(None);
446 }
447
448 best_match = Some(RegexMatch {
449 regions: match_region,
450 context: pat_context,
451 pat_index,
452 from_with_prototype: from_with_proto,
453 would_loop: pop_would_loop,
454 });
455
456 if match_start == start && !pop_would_loop {
457 return Ok(best_match);
460 }
461 }
462 }
463 }
464 }
465 Ok(best_match)
466 }
467
468 fn search(
469 &self,
470 line: &str,
471 start: usize,
472 match_pat: &MatchPattern,
473 captures: Option<&(Region, String)>,
474 search: &mut LineSearch,
475 ) -> Option<Region> {
476 let match_ptr = match_pat as *const MatchPattern;
478
479 if let Some(maybe_region) = search.cache.get(&match_ptr) {
480 if let Some(ref region) = *maybe_region {
481 let match_start = region.pos(0).unwrap().0;
482 if match_start >= start {
483 return Some(region.clone());
486 }
487 } else {
488 return None;
490 }
491 }
492
493 let (regex, can_cache) = match (match_pat.has_captures, captures) {
494 (true, Some(captures)) => {
495 let (region, s) = captures;
496 (&match_pat.regex_with_refs(region, s), false)
497 }
498 _ => (match_pat.regex(), true),
499 };
500 let matched = regex.possible_with_ascii_bytes(search.ascii_bytes)
502 && regex.search_with_scratch(
503 line,
504 start,
505 line.len(),
506 &mut search.region,
507 &mut search.scratch,
508 );
509
510 if matched {
511 let (match_start, match_end) = search.region.pos(0).unwrap();
512 let does_something = match match_pat.operation {
514 MatchOperation::None => match_start != match_end,
515 MatchOperation::Push(_) => self.stack.len() < 100,
516 _ => true,
517 };
518 if can_cache && does_something {
519 search.cache.insert(match_pat, Some(search.region.clone()));
520 }
521 if does_something {
522 return Some(search.region.clone());
524 }
525 } else if can_cache {
526 search.cache.insert(match_pat, None);
527 }
528 None
529 }
530
531 fn exec_pattern<'a>(
533 &mut self,
534 line: &str,
535 reg_match: &RegexMatch<'a>,
536 level_context: &'a Context,
537 syntax_set: &'a SyntaxSet,
538 ops: &mut Vec<(usize, ScopeStackOp)>,
539 ) -> Result<bool, ParsingError> {
540 let (match_start, match_end) = reg_match.regions.pos(0).unwrap();
541 let context = reg_match.context;
542 let pat = context.match_at(reg_match.pat_index)?;
543 self.push_meta_ops(
546 true,
547 match_start,
548 level_context,
549 &pat.operation,
550 syntax_set,
551 ops,
552 )?;
553 for s in &pat.scope {
554 ops.push((match_start, ScopeStackOp::Push(*s)));
556 }
557 if let Some(ref capture_map) = pat.captures {
558 let mut map: Vec<((usize, i32), ScopeStackOp)> = Vec::new();
562 for &(cap_index, ref scopes) in capture_map.iter() {
563 if let Some((cap_start, cap_end)) = reg_match.regions.pos(cap_index) {
564 if cap_start == cap_end {
566 continue;
567 }
568 for scope in scopes.iter() {
570 map.push((
571 (cap_start, -((cap_end - cap_start) as i32)),
572 ScopeStackOp::Push(*scope),
573 ));
574 }
575 map.push(((cap_end, i32::MIN), ScopeStackOp::Pop(scopes.len())));
576 }
577 }
578 map.sort_by(|a, b| a.0.cmp(&b.0));
579 for ((index, _), op) in map.into_iter() {
580 ops.push((index, op));
581 }
582 }
583 if !pat.scope.is_empty() {
584 ops.push((match_end, ScopeStackOp::Pop(pat.scope.len())));
586 }
587 self.push_meta_ops(
588 false,
589 match_end,
590 level_context,
591 &pat.operation,
592 syntax_set,
593 ops,
594 )?;
595
596 self.perform_op(line, ®_match.regions, pat, syntax_set)
597 }
598
599 fn push_meta_ops(
600 &self,
601 initial: bool,
602 index: usize,
603 cur_context: &Context,
604 match_op: &MatchOperation,
605 syntax_set: &SyntaxSet,
606 ops: &mut Vec<(usize, ScopeStackOp)>,
607 ) -> Result<(), ParsingError> {
608 match *match_op {
613 MatchOperation::Pop => {
614 let v = if initial {
615 &cur_context.meta_content_scope
616 } else {
617 &cur_context.meta_scope
618 };
619 if !v.is_empty() {
620 ops.push((index, ScopeStackOp::Pop(v.len())));
621 }
622
623 if !initial && cur_context.clear_scopes.is_some() {
625 ops.push((index, ScopeStackOp::Restore))
626 }
627 }
628 MatchOperation::Push(ref context_refs) | MatchOperation::Set(ref context_refs) => {
633 let is_set = matches!(*match_op, MatchOperation::Set(_));
634 if initial {
636 if is_set && cur_context.clear_scopes.is_some() {
637 ops.push((index, ScopeStackOp::Restore));
639 }
640 for r in context_refs.iter() {
642 let ctx = r.resolve(syntax_set)?;
643
644 if !is_set {
645 if let Some(clear_amount) = ctx.clear_scopes {
646 ops.push((index, ScopeStackOp::Clear(clear_amount)));
647 }
648 }
649
650 for scope in ctx.meta_scope.iter() {
651 ops.push((index, ScopeStackOp::Push(*scope)));
652 }
653 }
654 } else {
655 let repush = (is_set
656 && (!cur_context.meta_scope.is_empty()
657 || !cur_context.meta_content_scope.is_empty()))
658 || context_refs.iter().any(|r| {
659 let ctx = r.resolve(syntax_set).unwrap();
660
661 !ctx.meta_content_scope.is_empty()
662 || (ctx.clear_scopes.is_some() && is_set)
663 });
664 if repush {
665 let mut num_to_pop: usize = context_refs
667 .iter()
668 .map(|r| {
669 let ctx = r.resolve(syntax_set).unwrap();
670 ctx.meta_scope.len()
671 })
672 .sum();
673
674 if is_set {
676 num_to_pop +=
677 cur_context.meta_content_scope.len() + cur_context.meta_scope.len();
678 }
679
680 if num_to_pop > 0 {
682 ops.push((index, ScopeStackOp::Pop(num_to_pop)));
683 }
684
685 for r in context_refs {
687 let ctx = r.resolve(syntax_set)?;
688
689 if is_set {
691 if let Some(clear_amount) = ctx.clear_scopes {
692 ops.push((index, ScopeStackOp::Clear(clear_amount)));
693 }
694 }
695
696 for scope in ctx.meta_scope.iter() {
697 ops.push((index, ScopeStackOp::Push(*scope)));
698 }
699 for scope in ctx.meta_content_scope.iter() {
700 ops.push((index, ScopeStackOp::Push(*scope)));
701 }
702 }
703 }
704 }
705 }
706 MatchOperation::None => (),
707 }
708
709 Ok(())
710 }
711
712 fn perform_op(
714 &mut self,
715 line: &str,
716 regions: &Region,
717 pat: &MatchPattern,
718 syntax_set: &SyntaxSet,
719 ) -> Result<bool, ParsingError> {
720 let (ctx_refs, old_proto_ids) = match pat.operation {
721 MatchOperation::Push(ref ctx_refs) => (ctx_refs, None),
722 MatchOperation::Set(ref ctx_refs) => {
723 (ctx_refs, self.stack.pop().map(|s| s.prototypes))
727 }
728 MatchOperation::Pop => {
729 self.stack.pop();
730 return Ok(true);
731 }
732 MatchOperation::None => return Ok(false),
733 };
734 for (i, r) in ctx_refs.iter().enumerate() {
735 let mut proto_ids = if i == 0 {
736 old_proto_ids.clone().unwrap_or_else(Vec::new)
739 } else {
740 Vec::new()
741 };
742 if i == ctx_refs.len() - 1 {
743 if let Some(ref p) = pat.with_prototype {
749 proto_ids.push(p.id()?);
750 }
751 }
752 let context_id = r.id()?;
753 let context = syntax_set.get_context(&context_id)?;
754 let captures = {
755 let mut uses_backrefs = context.uses_backrefs;
756 if !proto_ids.is_empty() {
757 uses_backrefs = uses_backrefs
758 || proto_ids
759 .iter()
760 .any(|id| syntax_set.get_context(id).unwrap().uses_backrefs);
761 }
762 if uses_backrefs {
763 Some((regions.clone(), line.to_owned()))
764 } else {
765 None
766 }
767 };
768 self.stack.push(StateLevel {
769 context: context_id,
770 prototypes: proto_ids,
771 captures,
772 });
773 }
774 Ok(true)
775 }
776}
777
778#[cfg(feature = "yaml-load")]
779#[cfg(test)]
780mod tests {
781 use super::*;
782 use crate::parsing::ScopeStackOp::{Clear, Pop, Push, Restore};
783 use crate::parsing::{Scope, ScopeStack, SyntaxSet, SyntaxSetBuilder};
784 use crate::util::debug_print_ops;
785 use crate::utils::testdata;
786
787 const TEST_SYNTAX: &str = include_str!("../../testdata/parser_tests.sublime-syntax");
788 #[test]
789 fn can_parse_simple() {
790 let ss = &*testdata::PACKAGES_SYN_SET;
791 let mut state = {
792 let syntax = ss.find_syntax_by_name("Ruby on Rails").unwrap();
793 ParseState::new(syntax)
794 };
795
796 let ops1 = ops(&mut state, "module Bob::Wow::Troll::Five; 5; end", &ss);
797 let test_ops1 = vec![
798 (0, Push(Scope::new("source.ruby.rails").unwrap())),
799 (0, Push(Scope::new("meta.module.ruby").unwrap())),
800 (0, Push(Scope::new("keyword.control.module.ruby").unwrap())),
801 (6, Pop(2)),
802 (6, Push(Scope::new("meta.module.ruby").unwrap())),
803 (7, Pop(1)),
804 (7, Push(Scope::new("meta.module.ruby").unwrap())),
805 (7, Push(Scope::new("entity.name.module.ruby").unwrap())),
806 (7, Push(Scope::new("support.other.namespace.ruby").unwrap())),
807 (10, Pop(1)),
808 (10, Push(Scope::new("punctuation.accessor.ruby").unwrap())),
809 ];
810 assert_eq!(&ops1[0..test_ops1.len()], &test_ops1[..]);
811
812 let ops2 = ops(&mut state, "def lol(wow = 5)", &ss);
813 let test_ops2 = vec![
814 (0, Push(Scope::new("meta.function.ruby").unwrap())),
815 (0, Push(Scope::new("keyword.control.def.ruby").unwrap())),
816 (3, Pop(2)),
817 (3, Push(Scope::new("meta.function.ruby").unwrap())),
818 (4, Push(Scope::new("entity.name.function.ruby").unwrap())),
819 (7, Pop(1)),
820 ];
821 assert_eq!(&ops2[0..test_ops2.len()], &test_ops2[..]);
822 }
823
824 #[test]
825 fn can_parse_yaml() {
826 let ps = &*testdata::PACKAGES_SYN_SET;
827 let mut state = {
828 let syntax = ps.find_syntax_by_name("YAML").unwrap();
829 ParseState::new(syntax)
830 };
831
832 assert_eq!(
833 ops(&mut state, "key: value\n", &ps),
834 vec![
835 (0, Push(Scope::new("source.yaml").unwrap())),
836 (
837 0,
838 Push(Scope::new("string.unquoted.plain.out.yaml").unwrap())
839 ),
840 (0, Push(Scope::new("entity.name.tag.yaml").unwrap())),
841 (3, Pop(2)),
842 (
843 3,
844 Push(Scope::new("punctuation.separator.key-value.mapping.yaml").unwrap())
845 ),
846 (4, Pop(1)),
847 (
848 5,
849 Push(Scope::new("string.unquoted.plain.out.yaml").unwrap())
850 ),
851 (10, Pop(1)),
852 ]
853 );
854 }
855
856 #[test]
857 fn can_parse_includes() {
858 let ss = &*testdata::PACKAGES_SYN_SET;
859 let mut state = {
860 let syntax = ss.find_syntax_by_name("HTML (Rails)").unwrap();
861 ParseState::new(syntax)
862 };
863
864 let ops = ops(&mut state, "<script>var lol = '<% def wow(", &ss);
865
866 let mut test_stack = ScopeStack::new();
867 test_stack.push(Scope::new("text.html.ruby").unwrap());
868 test_stack.push(Scope::new("text.html.basic").unwrap());
869 test_stack.push(Scope::new("source.js.embedded.html").unwrap());
870 test_stack.push(Scope::new("source.js").unwrap());
871 test_stack.push(Scope::new("string.quoted.single.js").unwrap());
872 test_stack.push(Scope::new("source.ruby.rails.embedded.html").unwrap());
873 test_stack.push(Scope::new("meta.function.parameters.ruby").unwrap());
874
875 let mut stack = ScopeStack::new();
876 for (_, op) in ops.iter() {
877 stack.apply(op).expect("#[cfg(test)]");
878 }
879 assert_eq!(stack, test_stack);
880 }
881
882 #[test]
883 fn can_parse_backrefs() {
884 let ss = &*testdata::PACKAGES_SYN_SET;
885 let mut state = {
886 let syntax = ss.find_syntax_by_name("Ruby on Rails").unwrap();
887 ParseState::new(syntax)
888 };
889
890 assert_eq!(
894 ops(&mut state, "lol = <<-SQL.strip", &ss),
895 vec![
896 (0, Push(Scope::new("source.ruby.rails").unwrap())),
897 (
898 4,
899 Push(Scope::new("keyword.operator.assignment.ruby").unwrap())
900 ),
901 (5, Pop(1)),
902 (
903 6,
904 Push(Scope::new("string.unquoted.embedded.sql.ruby").unwrap())
905 ),
906 (
907 6,
908 Push(Scope::new("punctuation.definition.string.begin.ruby").unwrap())
909 ),
910 (12, Pop(1)),
911 (12, Pop(1)),
912 (
913 12,
914 Push(Scope::new("string.unquoted.embedded.sql.ruby").unwrap())
915 ),
916 (12, Push(Scope::new("text.sql.embedded.ruby").unwrap())),
917 (12, Clear(ClearAmount::TopN(2))),
918 (12, Push(Scope::new("punctuation.accessor.ruby").unwrap())),
919 (13, Pop(1)),
920 (18, Restore),
921 ]
922 );
923
924 assert_eq!(ops(&mut state, "wow", &ss), vec![]);
925
926 assert_eq!(
927 ops(&mut state, "SQL", &ss),
928 vec![
929 (0, Pop(1)),
930 (
931 0,
932 Push(Scope::new("punctuation.definition.string.end.ruby").unwrap())
933 ),
934 (3, Pop(1)),
935 (3, Pop(1)),
936 ]
937 );
938 }
939
940 #[test]
941 fn can_parse_preprocessor_rules() {
942 let ss = &*testdata::PACKAGES_SYN_SET;
943 let mut state = {
944 let syntax = ss.find_syntax_by_name("C").unwrap();
945 ParseState::new(syntax)
946 };
947
948 assert_eq!(
949 ops(&mut state, "#ifdef FOO", &ss),
950 vec![
951 (0, Push(Scope::new("source.c").unwrap())),
952 (0, Push(Scope::new("meta.preprocessor.c").unwrap())),
953 (0, Push(Scope::new("keyword.control.import.c").unwrap())),
954 (6, Pop(1)),
955 (10, Pop(1)),
956 ]
957 );
958 assert_eq!(
959 ops(&mut state, "{", &ss),
960 vec![
961 (0, Push(Scope::new("meta.block.c").unwrap())),
962 (
963 0,
964 Push(Scope::new("punctuation.section.block.begin.c").unwrap())
965 ),
966 (1, Pop(1)),
967 ]
968 );
969 assert_eq!(
970 ops(&mut state, "#else", &ss),
971 vec![
972 (0, Push(Scope::new("meta.preprocessor.c").unwrap())),
973 (0, Push(Scope::new("keyword.control.import.c").unwrap())),
974 (5, Pop(1)),
975 (5, Pop(1)),
976 ]
977 );
978 assert_eq!(
979 ops(&mut state, "{", &ss),
980 vec![
981 (0, Push(Scope::new("meta.block.c").unwrap())),
982 (
983 0,
984 Push(Scope::new("punctuation.section.block.begin.c").unwrap())
985 ),
986 (1, Pop(1)),
987 ]
988 );
989 assert_eq!(
990 ops(&mut state, "#endif", &ss),
991 vec![
992 (0, Pop(1)),
993 (0, Push(Scope::new("meta.block.c").unwrap())),
994 (0, Push(Scope::new("meta.preprocessor.c").unwrap())),
995 (0, Push(Scope::new("keyword.control.import.c").unwrap())),
996 (6, Pop(2)),
997 (6, Pop(2)),
998 (6, Push(Scope::new("meta.block.c").unwrap())),
999 ]
1000 );
1001 assert_eq!(
1002 ops(&mut state, " foo;", &ss),
1003 vec![
1004 (7, Push(Scope::new("punctuation.terminator.c").unwrap())),
1005 (8, Pop(1)),
1006 ]
1007 );
1008 assert_eq!(
1009 ops(&mut state, "}", &ss),
1010 vec![
1011 (
1012 0,
1013 Push(Scope::new("punctuation.section.block.end.c").unwrap())
1014 ),
1015 (1, Pop(1)),
1016 (1, Pop(1)),
1017 ]
1018 );
1019 }
1020
1021 #[test]
1022 fn can_parse_issue25() {
1023 let ss = &*testdata::PACKAGES_SYN_SET;
1024 let mut state = {
1025 let syntax = ss.find_syntax_by_name("C").unwrap();
1026 ParseState::new(syntax)
1027 };
1028
1029 assert_eq!(ops(&mut state, "struct{estruct", &ss).len(), 10);
1031 }
1032
1033 #[test]
1034 fn can_compare_parse_states() {
1035 let ss = &*testdata::PACKAGES_SYN_SET;
1036 let syntax = ss.find_syntax_by_name("Java").unwrap();
1037 let mut state1 = ParseState::new(syntax);
1038 let mut state2 = ParseState::new(syntax);
1039
1040 assert_eq!(ops(&mut state1, "class Foo {", &ss).len(), 11);
1041 assert_eq!(ops(&mut state2, "class Fooo {", &ss).len(), 11);
1042
1043 assert_eq!(state1, state2);
1044 ops(&mut state1, "}", &ss);
1045 assert_ne!(state1, state2);
1046 }
1047
1048 #[test]
1049 fn owned_heap_accounts_for_captured_delimiters_and_prototypes() {
1050 let definition = SyntaxDefinition::load_from_str(
1051 r#"name: Captured delimiters
1052scope: source.capture-test
1053contexts:
1054 main:
1055 - match: '^BEGIN ([A-Z]+)'
1056 push: body
1057 with_prototype:
1058 - match: 'SPECIAL'
1059 scope: keyword.control.capture-test
1060 body:
1061 - meta_scope: string.body.capture-test
1062 - match: '^END-\1'
1063 pop: true
1064 - match: '.+'
1065"#,
1066 true,
1067 None,
1068 )
1069 .unwrap();
1070 let mut builder = SyntaxSetBuilder::new();
1071 builder.add(definition);
1072 let syntaxes = builder.build();
1073 let mut state = ParseState::new(&syntaxes.syntaxes()[0]);
1074 let delimiter = "STOP".repeat(1024);
1075 let opening = format!("BEGIN {delimiter}\n");
1076 state.parse_line(&opening, &syntaxes).unwrap();
1077 let captured = state
1078 .stack
1079 .iter()
1080 .find(|level| level.captures.is_some())
1081 .expect("the delimiter must remain in parser state");
1082 assert!(!captured.prototypes.is_empty());
1083 let (region, text) = captured.captures.as_ref().unwrap();
1084 assert_eq!(text, &opening);
1085 if cfg!(feature = "regex-onig") {
1086 assert_eq!(state.owned_heap_bytes(), None);
1087 } else {
1088 let minimum = state.stack.capacity() * std::mem::size_of::<StateLevel>()
1089 + state.proto_starts.capacity() * std::mem::size_of::<usize>()
1090 + captured.prototypes.capacity() * std::mem::size_of::<ContextId>()
1091 + text.capacity()
1092 + region.owned_heap_bytes().unwrap();
1093 assert!(state.owned_heap_bytes().unwrap() >= minimum);
1094 }
1095 let mut restored = state.clone();
1096 assert_eq!(restored, state);
1097 for line in ["END-WRONG\n".to_owned(), format!("END-{delimiter}\n")] {
1098 assert_eq!(
1099 state.parse_line(&line, &syntaxes).unwrap(),
1100 restored.parse_line(&line, &syntaxes).unwrap()
1101 );
1102 assert_eq!(state, restored);
1103 }
1104 assert!(state.stack.iter().all(|level| level.captures.is_none()));
1105 assert!(state.owned_heap_bytes().is_some());
1106 }
1107
1108 #[test]
1109 fn can_parse_non_nested_clear_scopes() {
1110 let line = "'hello #simple_cleared_scopes_test world test \\n '";
1111 let expect = [
1112 "<source.test>, <example.meta-scope.after-clear-scopes.example>, <example.pushes-clear-scopes.example>",
1113 "<source.test>, <example.meta-scope.after-clear-scopes.example>, <example.pops-clear-scopes.example>",
1114 "<source.test>, <string.quoted.single.example>, <constant.character.escape.example>",
1115 ];
1116 expect_scope_stacks(line, &expect, TEST_SYNTAX);
1117 }
1118
1119 #[test]
1120 fn can_parse_non_nested_too_many_clear_scopes() {
1121 let line = "'hello #too_many_cleared_scopes_test world test \\n '";
1122 let expect = [
1123 "<example.meta-scope.after-clear-scopes.example>, <example.pushes-clear-scopes.example>",
1124 "<example.meta-scope.after-clear-scopes.example>, <example.pops-clear-scopes.example>",
1125 "<source.test>, <string.quoted.single.example>, <constant.character.escape.example>",
1126 ];
1127 expect_scope_stacks(line, &expect, TEST_SYNTAX);
1128 }
1129
1130 #[test]
1131 fn can_parse_nested_clear_scopes() {
1132 let line = "'hello #nested_clear_scopes_test world foo bar test \\n '";
1133 let expect = [
1134 "<source.test>, <example.meta-scope.after-clear-scopes.example>, <example.pushes-clear-scopes.example>",
1135 "<source.test>, <example.meta-scope.cleared-previous-meta-scope.example>, <foo>",
1136 "<source.test>, <example.meta-scope.after-clear-scopes.example>, <example.pops-clear-scopes.example>",
1137 "<source.test>, <string.quoted.single.example>, <constant.character.escape.example>",
1138 ];
1139 expect_scope_stacks(line, &expect, TEST_SYNTAX);
1140 }
1141
1142 #[test]
1143 fn can_parse_infinite_loop() {
1144 let line = "#infinite_loop_test 123";
1145 let expect = ["<source.test>, <constant.numeric.test>"];
1146 expect_scope_stacks(line, &expect, TEST_SYNTAX);
1147 }
1148
1149 #[test]
1150 fn can_parse_infinite_seeming_loop() {
1151 let line = "#infinite_seeming_loop_test hello";
1154 let expect = [
1155 "<source.test>, <keyword.test>",
1156 "<source.test>, <test>, <string.unquoted.test>",
1157 "<source.test>, <test>, <keyword.control.test>",
1158 ];
1159 expect_scope_stacks(line, &expect, TEST_SYNTAX);
1160 }
1161
1162 #[test]
1163 fn can_parse_prototype_that_pops_main() {
1164 let syntax = r#"
1165name: test
1166scope: source.test
1167contexts:
1168 prototype:
1169 # This causes us to pop out of the main context. Sublime Text handles that
1170 # by pushing main back automatically.
1171 - match: (?=!)
1172 pop: true
1173 main:
1174 - match: foo
1175 scope: test.good
1176"#;
1177
1178 let line = "foo!";
1179 let expect = ["<source.test>, <test.good>"];
1180 expect_scope_stacks(line, &expect, syntax);
1181 }
1182
1183 #[test]
1184 fn can_parse_syntax_with_newline_in_character_class() {
1185 let syntax = r#"
1186name: test
1187scope: source.test
1188contexts:
1189 main:
1190 - match: foo[\n]
1191 scope: foo.end
1192 - match: foo
1193 scope: foo.any
1194"#;
1195
1196 let line = "foo";
1197 let expect = ["<source.test>, <foo.end>"];
1198 expect_scope_stacks(line, &expect, syntax);
1199
1200 let line = "foofoofoo";
1201 let expect = [
1202 "<source.test>, <foo.any>",
1203 "<source.test>, <foo.any>",
1204 "<source.test>, <foo.end>",
1205 ];
1206 expect_scope_stacks(line, &expect, syntax);
1207 }
1208
1209 #[test]
1210 fn can_parse_issue120() {
1211 let syntax = SyntaxDefinition::load_from_str(
1212 include_str!("../../testdata/embed_escape_test.sublime-syntax"),
1213 false,
1214 None,
1215 )
1216 .unwrap();
1217
1218 let line1 = "\"abctest\" foobar";
1219 let expect1 = [
1220 "<meta.attribute-with-value.style.html>, <string.quoted.double>, <punctuation.definition.string.begin.html>",
1221 "<meta.attribute-with-value.style.html>, <source.css>",
1222 "<meta.attribute-with-value.style.html>, <string.quoted.double>, <punctuation.definition.string.end.html>",
1223 "<meta.attribute-with-value.style.html>, <source.css>, <test.embedded>",
1224 "<top-level.test>",
1225 ];
1226
1227 expect_scope_stacks_with_syntax(line1, &expect1, syntax.clone());
1228
1229 let line2 = ">abctest</style>foobar";
1230 let expect2 = [
1231 "<meta.tag.style.begin.html>, <punctuation.definition.tag.end.html>",
1232 "<source.css.embedded.html>, <test.embedded>",
1233 "<top-level.test>",
1234 ];
1235 expect_scope_stacks_with_syntax(line2, &expect2, syntax);
1236 }
1237
1238 #[test]
1239 fn can_parse_non_consuming_pop_that_would_loop() {
1240 let syntax = r#"
1242name: test
1243scope: source.test
1244contexts:
1245 main:
1246 # This makes us go into "test" without consuming any characters
1247 - match: (?=hello)
1248 push: test
1249 test:
1250 # If we used this match, we'd go back to "main" without consuming anything,
1251 # and then back into "test", infinitely looping. ST detects this at this
1252 # point and ignores this match until at least one character matched.
1253 - match: (?!world)
1254 pop: true
1255 - match: \w+
1256 scope: test.matched
1257"#;
1258
1259 let line = "hello";
1260 let expect = ["<source.test>, <test.matched>"];
1261 expect_scope_stacks(line, &expect, syntax);
1262 }
1263
1264 #[test]
1265 fn can_parse_non_consuming_set_and_pop_that_would_loop() {
1266 let syntax = r#"
1267name: test
1268scope: source.test
1269contexts:
1270 main:
1271 # This makes us go into "a" without advancing
1272 - match: (?=test)
1273 push: a
1274 a:
1275 # This makes us go into "b" without advancing
1276 - match: (?=t)
1277 set: b
1278 b:
1279 # If we used this match, we'd go back to "main" without having advanced,
1280 # which means we'd have an infinite loop like with the previous test.
1281 # So even for a "set", we have to check if we're advancing or not.
1282 - match: (?=t)
1283 pop: true
1284 - match: \w+
1285 scope: test.matched
1286"#;
1287
1288 let line = "test";
1289 let expect = ["<source.test>, <test.matched>"];
1290 expect_scope_stacks(line, &expect, syntax);
1291 }
1292
1293 #[test]
1294 fn can_parse_non_consuming_set_after_consuming_push_that_does_not_loop() {
1295 let syntax = r#"
1296name: test
1297scope: source.test
1298contexts:
1299 main:
1300 # This makes us go into "a", but we consumed a character
1301 - match: t
1302 push: a
1303 - match: \w+
1304 scope: test.matched
1305 a:
1306 # This makes us go into "b" without consuming
1307 - match: (?=e)
1308 set: b
1309 b:
1310 # This match does not result in an infinite loop because we already consumed
1311 # a character to get into "a", so it's ok to pop back into "main".
1312 - match: (?=e)
1313 pop: true
1314"#;
1315
1316 let line = "test";
1317 let expect = ["<source.test>, <test.matched>"];
1318 expect_scope_stacks(line, &expect, syntax);
1319 }
1320
1321 #[test]
1322 fn can_parse_non_consuming_set_after_consuming_set_that_does_not_loop() {
1323 let syntax = r#"
1324name: test
1325scope: source.test
1326contexts:
1327 main:
1328 - match: (?=hello)
1329 push: a
1330 - match: \w+
1331 scope: test.matched
1332 a:
1333 - match: h
1334 set: b
1335 b:
1336 - match: (?=e)
1337 set: c
1338 c:
1339 # This is not an infinite loop because "a" consumed a character, so we can
1340 # actually pop back into main and then match the rest of the input.
1341 - match: (?=e)
1342 pop: true
1343"#;
1344
1345 let line = "hello";
1346 let expect = ["<source.test>, <test.matched>"];
1347 expect_scope_stacks(line, &expect, syntax);
1348 }
1349
1350 #[test]
1351 fn can_parse_non_consuming_pop_that_would_loop_at_end_of_line() {
1352 let syntax = r#"
1353name: test
1354scope: source.test
1355contexts:
1356 main:
1357 # This makes us go into "test" without consuming, even at the end of line
1358 - match: ""
1359 push: test
1360 test:
1361 - match: ""
1362 pop: true
1363 - match: \w+
1364 scope: test.matched
1365"#;
1366
1367 let line = "hello";
1368 let expect = ["<source.test>, <test.matched>"];
1369 expect_scope_stacks(line, &expect, syntax);
1370 }
1371
1372 #[test]
1373 fn can_parse_empty_but_consuming_set_that_does_not_loop() {
1374 let syntax = r#"
1375name: test
1376scope: source.test
1377contexts:
1378 main:
1379 - match: (?=hello)
1380 push: a
1381 - match: ello
1382 scope: test.good
1383 a:
1384 # This is an empty match, but it consumed a character (the "h")
1385 - match: (?=e)
1386 set: b
1387 b:
1388 # .. so it's ok to pop back to main from here
1389 - match: ""
1390 pop: true
1391 - match: ello
1392 scope: test.bad
1393"#;
1394
1395 let line = "hello";
1396 let expect = ["<source.test>, <test.good>"];
1397 expect_scope_stacks(line, &expect, syntax);
1398 }
1399
1400 #[test]
1401 fn can_parse_non_consuming_pop_that_does_not_loop() {
1402 let syntax = r#"
1403name: test
1404scope: source.test
1405contexts:
1406 main:
1407 # This is a non-consuming push, so "b" will need to check for a
1408 # non-consuming pop
1409 - match: (?=hello)
1410 push: [b, a]
1411 - match: ello
1412 scope: test.good
1413 a:
1414 # This pop is ok, it consumed "h"
1415 - match: (?=e)
1416 pop: true
1417 b:
1418 # This is non-consuming, and we set to "c"
1419 - match: (?=e)
1420 set: c
1421 c:
1422 # It's ok to pop back to "main" here because we consumed a character in the
1423 # meantime.
1424 - match: ""
1425 pop: true
1426 - match: ello
1427 scope: test.bad
1428"#;
1429
1430 let line = "hello";
1431 let expect = ["<source.test>, <test.good>"];
1432 expect_scope_stacks(line, &expect, syntax);
1433 }
1434
1435 #[test]
1436 fn can_parse_non_consuming_pop_with_multi_push_that_does_not_loop() {
1437 let syntax = r#"
1438name: test
1439scope: source.test
1440contexts:
1441 main:
1442 - match: (?=hello)
1443 push: [b, a]
1444 - match: ello
1445 scope: test.good
1446 a:
1447 # This pop is ok, as we're not popping back to "main" yet (which would loop),
1448 # we're popping to "b"
1449 - match: ""
1450 pop: true
1451 - match: \w+
1452 scope: test.bad
1453 b:
1454 - match: \w+
1455 scope: test.good
1456"#;
1457
1458 let line = "hello";
1459 let expect = ["<source.test>, <test.good>"];
1460 expect_scope_stacks(line, &expect, syntax);
1461 }
1462
1463 #[test]
1464 fn can_parse_non_consuming_pop_of_recursive_context_that_does_not_loop() {
1465 let syntax = r#"
1466name: test
1467scope: source.test
1468contexts:
1469 main:
1470 - match: xxx
1471 scope: test.good
1472 - include: basic-identifiers
1473
1474 basic-identifiers:
1475 - match: '\w+::'
1476 scope: test.matched
1477 push: no-type-names
1478
1479 no-type-names:
1480 - include: basic-identifiers
1481 - match: \w+
1482 scope: test.matched.inside
1483 # This is a tricky one because when this is the best match,
1484 # we have two instances of "no-type-names" on the stack, so we're popping
1485 # back from "no-type-names" to another "no-type-names".
1486 - match: ''
1487 pop: true
1488"#;
1489
1490 let line = "foo::bar::* xxx";
1491 let expect = ["<source.test>, <test.good>"];
1492 expect_scope_stacks(line, &expect, syntax);
1493 }
1494
1495 #[test]
1496 fn can_parse_non_consuming_pop_order() {
1497 let syntax = r#"
1498name: test
1499scope: source.test
1500contexts:
1501 main:
1502 - match: (?=hello)
1503 push: test
1504 test:
1505 # This matches first
1506 - match: (?=e)
1507 push: good
1508 # But this (looping) match replaces it, because it's an earlier match
1509 - match: (?=h)
1510 pop: true
1511 # And this should not replace it, as it's a later match (only matches at
1512 # the same position can replace looping pops).
1513 - match: (?=o)
1514 push: bad
1515 good:
1516 - match: \w+
1517 scope: test.good
1518 bad:
1519 - match: \w+
1520 scope: test.bad
1521"#;
1522
1523 let line = "hello";
1524 let expect = ["<source.test>, <test.good>"];
1525 expect_scope_stacks(line, &expect, syntax);
1526 }
1527
1528 #[test]
1529 fn can_parse_prototype_with_embed() {
1530 let syntax = r#"
1531name: Javadoc
1532scope: text.html.javadoc
1533contexts:
1534 prototype:
1535 - match: \*
1536 scope: punctuation.definition.comment.javadoc
1537
1538 main:
1539 - meta_include_prototype: false
1540 - match: /\*\*
1541 scope: comment.block.documentation.javadoc punctuation.definition.comment.begin.javadoc
1542 embed: contents
1543 embed_scope: comment.block.documentation.javadoc text.html.javadoc
1544 escape: \*/
1545 escape_captures:
1546 0: comment.block.documentation.javadoc punctuation.definition.comment.end.javadoc
1547
1548 contents:
1549 - match: ''
1550"#;
1551
1552 let syntax = SyntaxDefinition::load_from_str(syntax, true, None).unwrap();
1553 expect_scope_stacks_with_syntax("/** * */", &["<comment.block.documentation.javadoc>, <punctuation.definition.comment.begin.javadoc>", "<comment.block.documentation.javadoc>, <text.html.javadoc>, <punctuation.definition.comment.javadoc>", "<comment.block.documentation.javadoc>, <punctuation.definition.comment.end.javadoc>"], syntax);
1554 }
1555
1556 #[test]
1557 fn can_parse_context_included_in_prototype_via_named_reference() {
1558 let syntax = r#"
1559scope: source.test
1560contexts:
1561 prototype:
1562 - match: a
1563 push: a
1564 - match: b
1565 scope: test.bad
1566 main:
1567 - match: unused
1568 # This context is included in the prototype (see `push: a`).
1569 # Because of that, ST doesn't apply the prototype to this context, so if
1570 # we're in here the "b" shouldn't match.
1571 a:
1572 - match: a
1573 scope: test.good
1574"#;
1575
1576 let stack_states = stack_states(parse("aa b", syntax));
1577 assert_eq!(
1578 stack_states,
1579 vec![
1580 "<source.test>",
1581 "<source.test>, <test.good>",
1582 "<source.test>",
1583 ],
1584 "Expected test.bad to not match"
1585 );
1586 }
1587
1588 #[test]
1589 fn can_parse_with_prototype_set() {
1590 let syntax = r#"%YAML 1.2
1591---
1592scope: source.test-set-with-proto
1593contexts:
1594 main:
1595 - match: a
1596 scope: a
1597 set: next1
1598 with_prototype:
1599 - match: '1'
1600 scope: '1'
1601 - match: '2'
1602 scope: '2'
1603 - match: '3'
1604 scope: '3'
1605 - match: '4'
1606 scope: '4'
1607 - match: '5'
1608 scope: '5'
1609 set: [next3, next2]
1610 with_prototype:
1611 - match: c
1612 scope: cwith
1613 next1:
1614 - match: b
1615 scope: b
1616 set: next2
1617 next2:
1618 - match: c
1619 scope: c
1620 push: next3
1621 - match: e
1622 scope: e
1623 pop: true
1624 - match: f
1625 scope: f
1626 set: [next1, next2]
1627 next3:
1628 - match: d
1629 scope: d
1630 - match: (?=e)
1631 pop: true
1632 - match: c
1633 scope: cwithout
1634"#;
1635
1636 expect_scope_stacks_with_syntax(
1637 "a1b2c3d4e5",
1638 &[
1639 "<a>", "<1>", "<b>", "<2>", "<c>", "<3>", "<d>", "<4>", "<e>", "<5>",
1640 ],
1641 SyntaxDefinition::load_from_str(syntax, true, None).unwrap(),
1642 );
1643 expect_scope_stacks_with_syntax(
1644 "5cfcecbedcdea",
1645 &[
1646 "<5>",
1647 "<cwith>",
1648 "<f>",
1649 "<e>",
1650 "<b>",
1651 "<d>",
1652 "<cwithout>",
1653 "<a>",
1654 ],
1655 SyntaxDefinition::load_from_str(syntax, true, None).unwrap(),
1656 );
1657 }
1658
1659 #[test]
1660 fn can_parse_issue176() {
1661 let syntax = r#"
1662scope: source.dummy
1663contexts:
1664 main:
1665 - match: (test)(?=(foo))(f)
1666 captures:
1667 1: test
1668 2: ignored
1669 3: f
1670 push:
1671 - match: (oo)
1672 captures:
1673 1: keyword
1674"#;
1675
1676 let syntax = SyntaxDefinition::load_from_str(syntax, true, None).unwrap();
1677 expect_scope_stacks_with_syntax(
1678 "testfoo",
1679 &["<test>", "<f>", "<keyword>"],
1680 syntax,
1681 );
1682 }
1683
1684 #[test]
1685 fn can_parse_two_with_prototypes_at_same_stack_level() {
1686 let syntax_yamlstr = r#"
1687%YAML 1.2
1688---
1689# See http://www.sublimetext.com/docs/3/syntax.html
1690scope: source.example-wp
1691contexts:
1692 main:
1693 - match: a
1694 scope: a
1695 push:
1696 - match: b
1697 scope: b
1698 set:
1699 - match: c
1700 scope: c
1701 with_prototype:
1702 - match: '2'
1703 scope: '2'
1704 with_prototype:
1705 - match: '1'
1706 scope: '1'
1707"#;
1708
1709 let syntax = SyntaxDefinition::load_from_str(syntax_yamlstr, true, None).unwrap();
1710 expect_scope_stacks_with_syntax("abc12", &["<1>", "<2>"], syntax);
1711 }
1712
1713 #[test]
1714 fn can_parse_two_with_prototypes_at_same_stack_level_set_multiple() {
1715 let syntax_yamlstr = r#"
1716%YAML 1.2
1717---
1718# See http://www.sublimetext.com/docs/3/syntax.html
1719scope: source.example-wp
1720contexts:
1721 main:
1722 - match: a
1723 scope: a
1724 push:
1725 - match: b
1726 scope: b
1727 set: [context1, context2, context3]
1728 with_prototype:
1729 - match: '2'
1730 scope: '2'
1731 with_prototype:
1732 - match: '1'
1733 scope: '1'
1734 - match: '1'
1735 scope: digit1
1736 - match: '2'
1737 scope: digit2
1738 context1:
1739 - match: e
1740 scope: e
1741 pop: true
1742 - match: '2'
1743 scope: digit2
1744 context2:
1745 - match: d
1746 scope: d
1747 pop: true
1748 - match: '2'
1749 scope: digit2
1750 context3:
1751 - match: c
1752 scope: c
1753 pop: true
1754"#;
1755
1756 let syntax = SyntaxDefinition::load_from_str(syntax_yamlstr, true, None).unwrap();
1757 expect_scope_stacks_with_syntax("ab12", &["<1>", "<2>"], syntax.clone());
1758 expect_scope_stacks_with_syntax("abc12", &["<1>", "<digit2>"], syntax.clone());
1759 expect_scope_stacks_with_syntax("abcd12", &["<1>", "<digit2>"], syntax.clone());
1760 expect_scope_stacks_with_syntax("abcde12", &["<digit1>", "<digit2>"], syntax);
1761 }
1762
1763 #[test]
1764 fn can_parse_two_with_prototypes_at_same_stack_level_updated_captures() {
1765 let syntax_yamlstr = r#"
1766%YAML 1.2
1767---
1768# See http://www.sublimetext.com/docs/3/syntax.html
1769scope: source.example-wp
1770contexts:
1771 main:
1772 - match: (a)
1773 scope: a
1774 push:
1775 - match: (b)
1776 scope: b
1777 set:
1778 - match: c
1779 scope: c
1780 with_prototype:
1781 - match: d
1782 scope: d
1783 with_prototype:
1784 - match: \1
1785 scope: '1'
1786 pop: true
1787"#;
1788
1789 let syntax = SyntaxDefinition::load_from_str(syntax_yamlstr, true, None).unwrap();
1790 expect_scope_stacks_with_syntax("aa", &["<a>", "<1>"], syntax.clone());
1791 expect_scope_stacks_with_syntax("abcdb", &["<a>", "<b>", "<c>", "<d>", "<1>"], syntax);
1792 }
1793
1794 #[test]
1795 fn can_parse_two_with_prototypes_at_same_stack_level_updated_captures_ignore_unexisting() {
1796 let syntax_yamlstr = r#"
1797%YAML 1.2
1798---
1799# See http://www.sublimetext.com/docs/3/syntax.html
1800scope: source.example-wp
1801contexts:
1802 main:
1803 - match: (a)(-)
1804 scope: a
1805 push:
1806 - match: (b)
1807 scope: b
1808 set:
1809 - match: c
1810 scope: c
1811 with_prototype:
1812 - match: d
1813 scope: d
1814 with_prototype:
1815 - match: \2
1816 scope: '2'
1817 pop: true
1818 - match: \1
1819 scope: '1'
1820 pop: true
1821"#;
1822
1823 let syntax = SyntaxDefinition::load_from_str(syntax_yamlstr, true, None).unwrap();
1824 expect_scope_stacks_with_syntax("a--", &["<a>", "<2>"], syntax.clone());
1825 expect_scope_stacks_with_syntax("a-bcdba-", &["<a>", "<b>"], syntax);
1828 }
1829
1830 #[test]
1831 fn can_parse_syntax_with_eol_and_newline() {
1832 let syntax = r#"
1833name: test
1834scope: source.test
1835contexts:
1836 main:
1837 - match: foo$\n
1838 scope: foo.newline
1839"#;
1840
1841 let line = "foo";
1842 let expect = ["<source.test>, <foo.newline>"];
1843 expect_scope_stacks(line, &expect, syntax);
1844 }
1845
1846 #[test]
1847 fn can_parse_syntax_with_eol_only() {
1848 let syntax = r#"
1849name: test
1850scope: source.test
1851contexts:
1852 main:
1853 - match: foo$
1854 scope: foo.newline
1855"#;
1856
1857 let line = "foo";
1858 let expect = ["<source.test>, <foo.newline>"];
1859 expect_scope_stacks(line, &expect, syntax);
1860 }
1861
1862 #[test]
1863 fn can_parse_syntax_with_beginning_of_line() {
1864 let syntax = r#"
1865name: test
1866scope: source.test
1867contexts:
1868 main:
1869 - match: \w+
1870 scope: word
1871 push:
1872 # this should not match at the end of the line
1873 - match: ^\s*$
1874 pop: true
1875 - match: =+
1876 scope: heading
1877 pop: true
1878 - match: .*
1879 scope: other
1880"#;
1881
1882 let syntax_newlines = SyntaxDefinition::load_from_str(syntax, true, None).unwrap();
1883 let syntax_set = link(syntax_newlines);
1884
1885 let mut state = ParseState::new(&syntax_set.syntaxes()[0]);
1886 assert_eq!(
1887 ops(&mut state, "foo\n", &syntax_set),
1888 vec![
1889 (0, Push(Scope::new("source.test").unwrap())),
1890 (0, Push(Scope::new("word").unwrap())),
1891 (3, Pop(1))
1892 ]
1893 );
1894 assert_eq!(
1895 ops(&mut state, "===\n", &syntax_set),
1896 vec![(0, Push(Scope::new("heading").unwrap())), (3, Pop(1))]
1897 );
1898
1899 assert_eq!(
1900 ops(&mut state, "bar\n", &syntax_set),
1901 vec![(0, Push(Scope::new("word").unwrap())), (3, Pop(1))]
1902 );
1903 assert_eq!(ops(&mut state, "\n", &syntax_set), vec![]);
1905 assert_eq!(
1907 ops(&mut state, "====\n", &syntax_set),
1908 vec![(0, Push(Scope::new("other").unwrap())), (4, Pop(1))]
1909 );
1910 }
1911
1912 #[test]
1913 fn can_parse_syntax_with_comment_and_eol() {
1914 let syntax = r#"
1915name: test
1916scope: source.test
1917contexts:
1918 main:
1919 - match: (//).*$
1920 scope: comment.line.double-slash
1921"#;
1922
1923 let syntax_newlines = SyntaxDefinition::load_from_str(syntax, true, None).unwrap();
1924 let syntax_set = link(syntax_newlines);
1925
1926 let mut state = ParseState::new(&syntax_set.syntaxes()[0]);
1927 assert_eq!(
1928 ops(&mut state, "// foo\n", &syntax_set),
1929 vec![
1930 (0, Push(Scope::new("source.test").unwrap())),
1931 (0, Push(Scope::new("comment.line.double-slash").unwrap())),
1932 (6, Pop(1))
1936 ]
1937 );
1938 }
1939
1940 #[test]
1941 fn can_parse_text_with_unicode_to_skip() {
1942 let syntax = r#"
1943name: test
1944scope: source.test
1945contexts:
1946 main:
1947 - match: (?=.)
1948 push: test
1949 test:
1950 - match: (?=.)
1951 pop: true
1952 - match: x
1953 scope: test.good
1954"#;
1955
1956 expect_scope_stacks("\u{03C0}x", &["<source.test>, <test.good>"], syntax);
1958 expect_scope_stacks("\u{0800}x", &["<source.test>, <test.good>"], syntax);
1960 expect_scope_stacks("\u{1F600}x", &["<source.test>, <test.good>"], syntax);
1962 }
1963
1964 #[test]
1965 fn can_include_backrefs() {
1966 let syntax = SyntaxDefinition::load_from_str(
1967 r#"
1968 name: Backref Include Test
1969 scope: source.backrefinc
1970 contexts:
1971 main:
1972 - match: (a)
1973 scope: a
1974 push: context1
1975 context1:
1976 - include: context2
1977 context2:
1978 - match: \1
1979 scope: b
1980 pop: true
1981 "#,
1982 true,
1983 None,
1984 )
1985 .unwrap();
1986
1987 expect_scope_stacks_with_syntax("aa", &["<a>", "<b>"], syntax);
1988 }
1989
1990 #[test]
1991 fn can_include_nested_backrefs() {
1992 let syntax = SyntaxDefinition::load_from_str(
1993 r#"
1994 name: Backref Include Test
1995 scope: source.backrefinc
1996 contexts:
1997 main:
1998 - match: (a)
1999 scope: a
2000 push: context1
2001 context1:
2002 - include: context3
2003 context3:
2004 - include: context2
2005 context2:
2006 - match: \1
2007 scope: b
2008 pop: true
2009 "#,
2010 true,
2011 None,
2012 )
2013 .unwrap();
2014
2015 expect_scope_stacks_with_syntax("aa", &["<a>", "<b>"], syntax);
2016 }
2017
2018 #[test]
2019 fn can_avoid_infinite_stack_depth() {
2020 let syntax = SyntaxDefinition::load_from_str(
2021 r#"
2022 name: Stack Depth Test
2023 scope: source.stack_depth
2024 contexts:
2025 main:
2026 - match: (a)
2027 scope: a
2028 push: context1
2029
2030
2031 context1:
2032 - match: b
2033 scope: b
2034 - match: ''
2035 push: context1
2036 - match: ''
2037 pop: 1
2038 - match: c
2039 scope: c
2040 "#,
2041 true,
2042 None,
2043 )
2044 .unwrap();
2045
2046 let syntax_set = link(syntax);
2047 let mut state = ParseState::new(&syntax_set.syntaxes()[0]);
2048 expect_scope_stacks_for_ops(ops(&mut state, "a bc\n", &syntax_set), &["<a>"]);
2049 expect_scope_stacks_for_ops(ops(&mut state, "bc\n", &syntax_set), &["<b>"]);
2050 }
2051
2052 fn expect_scope_stacks(line_without_newline: &str, expect: &[&str], syntax: &str) {
2053 println!("Parsing with newlines");
2054 let line_with_newline = format!("{}\n", line_without_newline);
2055 let syntax_newlines = SyntaxDefinition::load_from_str(syntax, true, None).unwrap();
2056 expect_scope_stacks_with_syntax(&line_with_newline, expect, syntax_newlines);
2057
2058 println!("Parsing without newlines");
2059 let syntax_nonewlines = SyntaxDefinition::load_from_str(syntax, false, None).unwrap();
2060 expect_scope_stacks_with_syntax(line_without_newline, expect, syntax_nonewlines);
2061 }
2062
2063 fn expect_scope_stacks_with_syntax(line: &str, expect: &[&str], syntax: SyntaxDefinition) {
2064 let syntax_set = link(syntax);
2067 let mut state = ParseState::new(&syntax_set.syntaxes()[0]);
2068 let ops = ops(&mut state, line, &syntax_set);
2069 expect_scope_stacks_for_ops(ops, expect);
2070 }
2071
2072 fn expect_scope_stacks_for_ops(ops: Vec<(usize, ScopeStackOp)>, expect: &[&str]) {
2073 let mut criteria_met = Vec::new();
2074 for stack_str in stack_states(ops) {
2075 println!("{}", stack_str);
2076 for expectation in expect.iter() {
2077 if stack_str.contains(expectation) {
2078 criteria_met.push(expectation);
2079 }
2080 }
2081 }
2082 if let Some(missing) = expect.iter().find(|e| !criteria_met.contains(e)) {
2083 panic!("expected scope stack '{}' missing", missing);
2084 }
2085 }
2086
2087 fn parse(line: &str, syntax: &str) -> Vec<(usize, ScopeStackOp)> {
2088 let syntax = SyntaxDefinition::load_from_str(syntax, true, None).unwrap();
2089 let syntax_set = link(syntax);
2090
2091 let mut state = ParseState::new(&syntax_set.syntaxes()[0]);
2092 ops(&mut state, line, &syntax_set)
2093 }
2094
2095 fn link(syntax: SyntaxDefinition) -> SyntaxSet {
2096 let mut builder = SyntaxSetBuilder::new();
2097 builder.add(syntax);
2098 builder.build()
2099 }
2100
2101 fn ops(
2102 state: &mut ParseState,
2103 line: &str,
2104 syntax_set: &SyntaxSet,
2105 ) -> Vec<(usize, ScopeStackOp)> {
2106 let ops = state.parse_line(line, syntax_set).expect("#[cfg(test)]");
2107 debug_print_ops(line, &ops);
2108 ops
2109 }
2110
2111 fn stack_states(ops: Vec<(usize, ScopeStackOp)>) -> Vec<String> {
2112 let mut states = Vec::new();
2113 let mut stack = ScopeStack::new();
2114 for (_, op) in ops.iter() {
2115 stack.apply(op).expect("#[cfg(test)]");
2116 let scopes: Vec<String> = stack
2117 .as_slice()
2118 .iter()
2119 .map(|s| format!("{:?}", s))
2120 .collect();
2121 let stack_str = scopes.join(", ");
2122 states.push(stack_str);
2123 }
2124 states
2125 }
2126}