1use super::regex::{Regex, Region};
2use super::scope::*;
3use super::syntax_definition::*;
4use std::collections::HashMap;
5use std::error::Error;
6use std::ops::DerefMut;
7use std::path::Path;
8use yaml_rust2::yaml::Hash;
9use yaml_rust2::{Yaml, YamlLoader};
10
11#[derive(Debug, thiserror::Error)]
12#[non_exhaustive]
13pub enum ParseSyntaxError {
14 #[error("Invalid YAML file syntax: {0}")]
16 InvalidYaml(#[source] Box<dyn std::error::Error + Send + Sync>),
17 #[error("The file must contain at least one YAML document")]
19 EmptyFile,
20 #[error("Missing mandatory key in YAML file: {0}")]
22 MissingMandatoryKey(&'static str),
23 #[error("Error while compiling regex '{0}': {1}")]
25 RegexCompileError(String, #[source] Box<dyn Error + Send + Sync + 'static>),
26 #[error("Invalid scope: {0}")]
28 InvalidScope(ParseScopeError),
29 #[error("Invalid file reference")]
31 BadFileRef,
32 #[error("Context 'main' is missing")]
34 MainMissing,
35 #[error("Type mismatch")]
39 TypeMismatch,
40}
41
42fn get_key<'a, R, F: FnOnce(&'a Yaml) -> Option<R>>(
43 map: &'a Hash,
44 key: &'static str,
45 f: F,
46) -> Result<R, ParseSyntaxError> {
47 map.get(&Yaml::String(key.to_owned()))
48 .ok_or(ParseSyntaxError::MissingMandatoryKey(key))
49 .and_then(|x| f(x).ok_or(ParseSyntaxError::TypeMismatch))
50}
51
52fn str_to_scopes(s: &str, repo: &mut ScopeRepository) -> Result<Vec<Scope>, ParseSyntaxError> {
53 s.split_whitespace()
54 .map(|scope| repo.build(scope).map_err(ParseSyntaxError::InvalidScope))
55 .collect()
56}
57
58pub(crate) struct ParserState<'a> {
59 pub(crate) scope_repo: &'a mut ScopeRepository,
60 pub(crate) variables: HashMap<String, String>,
61 pub(crate) variable_regex: Regex,
62 pub(crate) backref_regex: Regex,
63 pub(crate) lines_include_newline: bool,
64 pub(crate) version: u32,
65 pub(crate) defer_regex_validation: bool,
71}
72
73static START_CONTEXT: &str = "
76__start:
77 - meta_include_prototype: false
78 - match: ''
79 push: __main
80__main:
81 - include: main
82";
83
84impl SyntaxDefinition {
85 pub fn load_from_str(
93 s: &str,
94 lines_include_newline: bool,
95 fallback_name: Option<&str>,
96 ) -> Result<SyntaxDefinition, ParseSyntaxError> {
97 let docs = match YamlLoader::load_from_str(s) {
98 Ok(x) => x,
99 Err(e) => return Err(ParseSyntaxError::InvalidYaml(Box::new(e))),
100 };
101 if docs.is_empty() {
102 return Err(ParseSyntaxError::EmptyFile);
103 }
104 let doc = &docs[0];
105 let mut scope_repo = lock_global_scope_repo();
106 SyntaxDefinition::parse_top_level(
107 doc,
108 scope_repo.deref_mut(),
109 lines_include_newline,
110 fallback_name,
111 )
112 }
113
114 fn parse_top_level(
115 doc: &Yaml,
116 scope_repo: &mut ScopeRepository,
117 lines_include_newline: bool,
118 fallback_name: Option<&str>,
119 ) -> Result<SyntaxDefinition, ParseSyntaxError> {
120 let h = doc.as_hash().ok_or(ParseSyntaxError::TypeMismatch)?;
121
122 let mut variables = HashMap::new();
123 if let Ok(map) = get_key(h, "variables", |x| x.as_hash()) {
124 for (key, value) in map.iter() {
125 if let (Some(key_str), Some(val_str)) = (key.as_str(), value.as_str()) {
126 variables.insert(key_str.to_owned(), val_str.to_owned());
127 }
128 }
129 }
130 let has_extends = get_key(h, "extends", Some).is_ok();
131 let empty_contexts = Hash::new();
132 let contexts_hash = match get_key(h, "contexts", |x| x.as_hash()) {
133 Ok(hash) => hash,
134 Err(_) if has_extends => &empty_contexts,
137 Err(e) => return Err(e),
138 };
139 let top_level_scope = scope_repo
140 .build(get_key(h, "scope", |x| x.as_str())?)
141 .map_err(ParseSyntaxError::InvalidScope)?;
142 let version = get_key(h, "version", |x| x.as_i64()).unwrap_or(1) as u32;
143
144 let mut state = ParserState {
145 scope_repo,
146 variables,
147 variable_regex: Regex::new(r"\{\{([A-Za-z0-9_]+)\}\}".into()),
148 backref_regex: Regex::new(r"\\\d".into()),
149 lines_include_newline,
150 version,
151 defer_regex_validation: has_extends,
152 };
153
154 let mut contexts = SyntaxDefinition::parse_contexts(contexts_hash, &mut state)?;
155 if !contexts.contains_key("main") && !has_extends {
156 return Err(ParseSyntaxError::MainMissing);
157 }
158
159 if contexts.contains_key("main") {
160 SyntaxDefinition::add_initial_contexts(&mut contexts, &mut state, top_level_scope);
161 }
162
163 let mut file_extensions = Vec::new();
164 for extension_key in &["file_extensions", "hidden_file_extensions"] {
165 if let Ok(v) = get_key(h, extension_key, |x| x.as_vec()) {
166 file_extensions.extend(v.iter().filter_map(|y| y.as_str().map(|s| s.to_owned())))
167 }
168 }
169
170 let extends = get_key(h, "extends", Some)
171 .ok()
172 .map(|y| {
173 if let Some(s) = y.as_str() {
174 vec![s.to_owned()]
175 } else if let Some(seq) = y.as_vec() {
176 seq.iter()
177 .filter_map(|v| v.as_str().map(|s| s.to_owned()))
178 .collect()
179 } else {
180 vec![]
181 }
182 })
183 .unwrap_or_default();
184
185 let defn = SyntaxDefinition {
186 name: get_key(h, "name", |x| x.as_str())
187 .unwrap_or_else(|_| fallback_name.unwrap_or("Unnamed"))
188 .to_owned(),
189 scope: top_level_scope,
190 file_extensions,
191 first_line_match: get_key(h, "first_line_match", |x| x.as_str())
193 .ok()
194 .map(|s| Self::resolve_variables(s, &state)),
195 hidden: get_key(h, "hidden", |x| x.as_bool()).unwrap_or(false),
196
197 variables: state.variables,
198 contexts,
199 extends,
200 version,
201 };
202 Ok(defn)
203 }
204
205 fn parse_contexts(
206 map: &Hash,
207 state: &mut ParserState<'_>,
208 ) -> Result<HashMap<String, Context>, ParseSyntaxError> {
209 let mut contexts = HashMap::new();
210 for (key, value) in map.iter() {
211 if let (Some(name), Some(val_vec)) = (key.as_str(), value.as_vec()) {
212 let mut namer = ContextNamer::new(name);
213 SyntaxDefinition::parse_context(val_vec, state, &mut contexts, &mut namer)?;
214 }
215 }
216
217 Ok(contexts)
218 }
219
220 fn parse_context(
221 vec: &[Yaml],
222 state: &mut ParserState<'_>,
224 contexts: &mut HashMap<String, Context>,
225 namer: &mut ContextNamer,
226 ) -> Result<String, ParseSyntaxError> {
227 let mut context = Context::new(None);
234 let name = namer.next();
235
236 for y in vec.iter() {
237 let map = y.as_hash().ok_or(ParseSyntaxError::TypeMismatch)?;
238
239 let mut is_special = false;
240 if let Ok(x) = get_key(map, "meta_scope", |x| x.as_str()) {
241 context.meta_scope = str_to_scopes(x, state.scope_repo)?;
242 is_special = true;
243 }
244 if let Ok(x) = get_key(map, "meta_content_scope", |x| x.as_str()) {
245 context.meta_content_scope = str_to_scopes(x, state.scope_repo)?;
246 is_special = true;
247 }
248 if let Ok(x) = get_key(map, "meta_include_prototype", |x| x.as_bool()) {
249 context.meta_include_prototype = Some(x);
250 is_special = true;
251 }
252 if let Ok(true) = get_key(map, "meta_prepend", |x| x.as_bool()) {
253 context.merge_mode = ContextMergeMode::Prepend;
254 is_special = true;
255 }
256 if let Ok(true) = get_key(map, "meta_append", |x| x.as_bool()) {
257 context.merge_mode = ContextMergeMode::Append;
258 is_special = true;
259 }
260 if let Ok(true) = get_key(map, "clear_scopes", |x| x.as_bool()) {
261 context.clear_scopes = Some(ClearAmount::All);
262 is_special = true;
263 }
264 if let Ok(x) = get_key(map, "clear_scopes", |x| x.as_i64()) {
265 context.clear_scopes = Some(ClearAmount::TopN(x as usize));
266 is_special = true;
267 }
268 if !is_special {
269 if let Ok(x) = get_key(map, "include", Some) {
270 let reference =
271 SyntaxDefinition::parse_reference(x, state, contexts, namer, false)?;
272 let apply_prototype =
273 get_key(map, "apply_prototype", |x| x.as_bool()).unwrap_or(false);
274 if apply_prototype {
275 context
276 .patterns
277 .push(Pattern::IncludeWithPrototype(reference));
278 } else {
279 context.patterns.push(Pattern::Include(reference));
280 }
281 } else {
282 let pattern =
283 SyntaxDefinition::parse_match_pattern(map, state, contexts, namer)?;
284 if pattern.has_captures {
285 context.uses_backrefs = true;
286 }
287 context.patterns.push(Pattern::Match(pattern));
288 }
289 }
290 }
291
292 contexts.insert(name.clone(), context);
293 Ok(name)
294 }
295
296 fn parse_reference(
297 y: &Yaml,
298 state: &mut ParserState<'_>,
299 contexts: &mut HashMap<String, Context>,
300 namer: &mut ContextNamer,
301 with_escape: bool,
302 ) -> Result<ContextReference, ParseSyntaxError> {
303 if let Some(s) = y.as_str() {
304 let parts: Vec<&str> = s.split('#').collect();
305 let sub_context = if parts.len() > 1 {
306 Some(parts[1].to_owned())
307 } else {
308 None
309 };
310 if parts[0].starts_with("scope:") {
311 Ok(ContextReference::ByScope {
312 scope: state
313 .scope_repo
314 .build(&parts[0][6..])
315 .map_err(ParseSyntaxError::InvalidScope)?,
316 sub_context,
317 with_escape,
318 })
319 } else if parts[0].ends_with(".sublime-syntax") {
320 let stem = Path::new(parts[0])
321 .file_stem()
322 .and_then(|x| x.to_str())
323 .ok_or(ParseSyntaxError::BadFileRef)?;
324 Ok(ContextReference::File {
325 name: stem.to_owned(),
326 sub_context,
327 with_escape,
328 })
329 } else {
330 Ok(ContextReference::Named(parts[0].to_owned()))
331 }
332 } else if let Some(v) = y.as_vec() {
333 let subname = SyntaxDefinition::parse_context(v, state, contexts, namer)?;
334 Ok(ContextReference::Inline(subname))
335 } else {
336 Err(ParseSyntaxError::TypeMismatch)
337 }
338 }
339
340 fn parse_match_pattern(
341 map: &Hash,
342 state: &mut ParserState<'_>,
343 contexts: &mut HashMap<String, Context>,
344 namer: &mut ContextNamer,
345 ) -> Result<MatchPattern, ParseSyntaxError> {
346 let raw_regex = get_key(map, "match", |x| x.as_str())?;
347 let raw_regex_owned = raw_regex.to_owned();
348 let regex_str = Self::parse_regex(raw_regex, state)?;
349
350 let scope = get_key(map, "scope", |x| x.as_str())
351 .ok()
352 .map(|s| str_to_scopes(s, state.scope_repo))
353 .unwrap_or_else(|| Ok(vec![]))?;
354
355 let captures = if let Ok(map) = get_key(map, "captures", |x| x.as_hash()) {
356 Some(Self::parse_captures(map, ®ex_str, state)?)
357 } else {
358 None
359 };
360
361 let mut has_captures = false;
362 let operation = if let Ok(y) = get_key(map, "pop", |y| {
363 y.as_i64().or(match y.as_bool() {
364 Some(true) => Some(1),
365 _ => None,
366 })
367 }) {
368 has_captures = state
370 .backref_regex
371 .search(®ex_str, 0, regex_str.len(), None, true);
372 if get_key(map, "embed", Some).is_ok() {
375 Self::parse_embed_op(map, state, contexts, namer, y as usize)?
376 } else if let Ok(b) = get_key(map, "branch", |x| x.as_vec()) {
377 let branch_point = get_key(map, "branch_point", |x| x.as_str())?;
378 let alternatives: Vec<ContextReference> = b
379 .iter()
380 .map(|item| {
381 SyntaxDefinition::parse_reference(item, state, contexts, namer, false)
382 })
383 .collect::<Result<_, _>>()?;
384 MatchOperation::Branch {
385 name: branch_point.to_owned(),
386 alternatives,
387 pop_count: y as usize,
388 }
389 } else if let Ok(s) = get_key(map, "set", Some) {
390 MatchOperation::Set {
392 ctx_refs: SyntaxDefinition::parse_pushargs(s, state, contexts, namer)?,
393 pop_count: y as usize,
394 }
395 } else {
396 MatchOperation::Pop(y as usize)
397 }
398 } else if let Ok(y) = get_key(map, "push", Some) {
399 MatchOperation::Push(SyntaxDefinition::parse_pushargs(y, state, contexts, namer)?)
400 } else if let Ok(y) = get_key(map, "set", Some) {
401 MatchOperation::Set {
402 ctx_refs: SyntaxDefinition::parse_pushargs(y, state, contexts, namer)?,
403 pop_count: 1,
404 }
405 } else if let Ok(y) = get_key(map, "branch", |x| x.as_vec()) {
406 let branch_point = get_key(map, "branch_point", |x| x.as_str())?;
407 let alternatives: Vec<ContextReference> = y
408 .iter()
409 .map(|item| SyntaxDefinition::parse_reference(item, state, contexts, namer, false))
410 .collect::<Result<_, _>>()?;
411 MatchOperation::Branch {
412 name: branch_point.to_owned(),
413 alternatives,
414 pop_count: 0,
415 }
416 } else if let Ok(y) = get_key(map, "fail", |x| x.as_str()) {
417 MatchOperation::Fail(y.to_owned())
418 } else if get_key(map, "embed", Some).is_ok() {
419 Self::parse_embed_op(map, state, contexts, namer, 0)?
420 } else {
421 MatchOperation::None
422 };
423
424 let with_prototype = if let Ok(v) = get_key(map, "with_prototype", |x| x.as_vec()) {
425 let subname = Self::parse_context(v, state, contexts, namer)?;
427 Some(ContextReference::Inline(subname))
428 } else {
429 None
430 };
431
432 let pattern = MatchPattern::new_with_raw(
433 has_captures,
434 regex_str,
435 raw_regex_owned,
436 scope,
437 captures,
438 operation,
439 with_prototype,
440 );
441
442 Ok(pattern)
443 }
444
445 fn parse_embed_op(
446 map: &Hash,
447 state: &mut ParserState<'_>,
448 contexts: &mut HashMap<String, Context>,
449 namer: &mut ContextNamer,
450 pop_count: usize,
451 ) -> Result<MatchOperation, ParseSyntaxError> {
452 let y = get_key(map, "embed", Some)?;
453 let v = get_key(map, "escape", Some)
454 .map_err(|_| ParseSyntaxError::MissingMandatoryKey("escape"))?;
455
456 let escape_raw = v.as_str().ok_or(ParseSyntaxError::TypeMismatch)?;
457 let escape_regex_str = Self::parse_regex(escape_raw, state)?;
458 let escape_has_captures =
459 state
460 .backref_regex
461 .search(&escape_regex_str, 0, escape_regex_str.len(), None, true);
462
463 let escape_captures = if let Ok(cap_map) = get_key(map, "escape_captures", |x| x.as_hash())
464 {
465 Some(Self::parse_captures(cap_map, &escape_regex_str, state)?)
466 } else {
467 None
468 };
469
470 let escape_info = EscapeInfo {
471 escape_regex: Regex::new(escape_regex_str),
472 has_captures: escape_has_captures,
473 escape_captures,
474 raw_escape_regex_str: Some(escape_raw.to_owned()),
475 };
476
477 let mut embed_contexts = Vec::new();
480
481 let has_embed_scope = get_key(map, "embed_scope", Some).is_ok();
483 if has_embed_scope {
484 let mut embed_scope_context_yaml = vec![];
485 let mut commands = Hash::new();
486 commands.insert(
487 Yaml::String("meta_include_prototype".to_string()),
488 Yaml::Boolean(false),
489 );
490 embed_scope_context_yaml.push(Yaml::Hash(commands));
491 if let Ok(s) = get_key(map, "embed_scope", Some) {
492 let mut commands2 = Hash::new();
493 commands2.insert(Yaml::String("meta_content_scope".to_string()), s.clone());
494 embed_scope_context_yaml.push(Yaml::Hash(commands2));
495 }
496 let mut match_map = Hash::new();
498 match_map.insert(
499 Yaml::String("match".to_string()),
500 Yaml::String(String::new()),
501 );
502 match_map.insert(Yaml::String("pop".to_string()), Yaml::Boolean(true));
503 embed_scope_context_yaml.push(Yaml::Hash(match_map));
504 let scope_ctx_name =
505 SyntaxDefinition::parse_context(&embed_scope_context_yaml, state, contexts, namer)?;
506 if state.version >= 2 {
508 if let Some(ctx) = contexts.get_mut(&scope_ctx_name) {
509 ctx.embed_scope_replaces = true;
510 }
511 }
512 embed_contexts.push(ContextReference::Inline(scope_ctx_name));
513 }
514
515 embed_contexts.push(SyntaxDefinition::parse_reference(
516 y, state, contexts, namer, true,
517 )?);
518
519 Ok(MatchOperation::Embed {
520 contexts: embed_contexts,
521 escape: escape_info,
522 pop_count,
523 })
524 }
525
526 fn parse_pushargs(
527 y: &Yaml,
528 state: &mut ParserState<'_>,
529 contexts: &mut HashMap<String, Context>,
530 namer: &mut ContextNamer,
531 ) -> Result<Vec<ContextReference>, ParseSyntaxError> {
532 if y.as_vec().is_some_and(|v| {
534 !v.is_empty()
535 && (v[0].as_str().is_some()
536 || (v[0].as_vec().is_some() && v[0].as_vec().unwrap()[0].as_hash().is_some()))
537 }) {
538 y.as_vec()
540 .unwrap()
541 .iter()
542 .map(|x| SyntaxDefinition::parse_reference(x, state, contexts, namer, false))
543 .collect()
544 } else {
545 let reference = SyntaxDefinition::parse_reference(y, state, contexts, namer, false)?;
546 Ok(vec![reference])
547 }
548 }
549
550 fn parse_regex(raw_regex: &str, state: &ParserState<'_>) -> Result<String, ParseSyntaxError> {
551 let regex = Self::resolve_variables(raw_regex, state);
552 let regex = replace_posix_char_classes(regex);
553 let regex = if state.lines_include_newline {
554 regex_for_newlines(regex)
555 } else {
556 regex_for_no_newlines(regex)
560 };
561 if !state.defer_regex_validation {
562 Self::try_compile_regex(®ex)?;
563 }
564 Ok(regex)
565 }
566
567 fn resolve_variables(raw_regex: &str, state: &ParserState<'_>) -> String {
568 let mut result = String::new();
569 let mut index = 0;
570 let mut region = Region::new();
571 while state.variable_regex.search(
572 raw_regex,
573 index,
574 raw_regex.len(),
575 Some(&mut region),
576 true,
577 ) {
578 let (begin, end) = region.pos(0).unwrap();
579
580 result.push_str(&raw_regex[index..begin]);
581
582 let var_pos = region.pos(1).unwrap();
583 let var_name = &raw_regex[var_pos.0..var_pos.1];
584 let var_raw = state
585 .variables
586 .get(var_name)
587 .map(String::as_ref)
588 .unwrap_or("");
589 let var_resolved = Self::resolve_variables(var_raw, state);
590 result.push_str(&var_resolved);
591
592 index = end;
593 }
594 if index < raw_regex.len() {
595 result.push_str(&raw_regex[index..]);
596 }
597 result
598 }
599
600 fn try_compile_regex(regex_str: &str) -> Result<(), ParseSyntaxError> {
601 let regex_str =
603 substitute_backrefs_in_regex(regex_str, |i| Some(format!("<placeholder_{}>", i)));
604
605 if let Some(error) = Regex::try_compile(®ex_str) {
606 Err(ParseSyntaxError::RegexCompileError(regex_str, error))
607 } else {
608 Ok(())
609 }
610 }
611
612 fn parse_captures(
613 map: &Hash,
614 regex_str: &str,
615 state: &mut ParserState<'_>,
616 ) -> Result<CaptureMapping, ParseSyntaxError> {
617 let valid_indexes = get_consuming_capture_indexes(regex_str);
618 let mut captures = Vec::new();
619 for (key, value) in map.iter() {
620 if let (Some(key_int), Some(val_str)) = (key.as_i64(), value.as_str()) {
621 if valid_indexes.contains(&(key_int as usize)) {
622 captures.push((key_int as usize, str_to_scopes(val_str, state.scope_repo)?));
623 }
624 }
625 }
626 Ok(captures)
627 }
628
629 pub(crate) fn add_initial_contexts(
636 contexts: &mut HashMap<String, Context>,
637 state: &mut ParserState<'_>,
638 top_level_scope: Scope,
639 ) {
640 let yaml_docs = YamlLoader::load_from_str(START_CONTEXT).unwrap();
641 let yaml = &yaml_docs[0];
642
643 let start_yaml: &[Yaml] = yaml["__start"].as_vec().unwrap();
644 SyntaxDefinition::parse_context(
645 start_yaml,
646 state,
647 contexts,
648 &mut ContextNamer::new("__start"),
649 )
650 .unwrap();
651 if let Some(start) = contexts.get_mut("__start") {
652 start.meta_content_scope = vec![top_level_scope];
653 }
654
655 let main_yaml: &[Yaml] = yaml["__main"].as_vec().unwrap();
656 SyntaxDefinition::parse_context(
657 main_yaml,
658 state,
659 contexts,
660 &mut ContextNamer::new("__main"),
661 )
662 .unwrap();
663
664 let meta_include_prototype = contexts["main"].meta_include_prototype;
665 let meta_scope = contexts["main"].meta_scope.clone();
666 let mut meta_content_scope = contexts["main"].meta_content_scope.clone();
676 if meta_content_scope.first() == Some(&top_level_scope) {
677 meta_content_scope.remove(0);
678 }
679
680 if let Some(outer_main) = contexts.get_mut("__main") {
681 outer_main.meta_include_prototype = meta_include_prototype;
682 outer_main.meta_scope = meta_scope;
683 outer_main.meta_content_scope = meta_content_scope;
684 }
685
686 if let Some(main) = contexts.get_mut("main") {
692 if main.meta_content_scope.first() != Some(&top_level_scope) {
693 main.meta_content_scope.insert(0, top_level_scope);
694 }
695 }
696 }
697}
698
699pub(crate) fn re_resolve_regex(
702 raw: &str,
703 variables: &HashMap<String, String>,
704 lines_include_newline: bool,
705) -> Result<String, ParseSyntaxError> {
706 let variable_regex = Regex::new(r"\{\{([A-Za-z0-9_]+)\}\}".into());
707 let state = ReResolveState {
708 variables,
709 variable_regex: &variable_regex,
710 };
711 let regex = re_resolve_variables(raw, &state);
712 let regex = replace_posix_char_classes(regex);
713 let regex = if lines_include_newline {
714 regex_for_newlines(regex)
715 } else {
716 regex_for_no_newlines(regex)
717 };
718 SyntaxDefinition::try_compile_regex(®ex)?;
719 Ok(regex)
720}
721
722struct ReResolveState<'a> {
723 variables: &'a HashMap<String, String>,
724 variable_regex: &'a Regex,
725}
726
727fn re_resolve_variables(raw_regex: &str, state: &ReResolveState<'_>) -> String {
728 let mut result = String::new();
729 let mut index = 0;
730 let mut region = Region::new();
731 while state
732 .variable_regex
733 .search(raw_regex, index, raw_regex.len(), Some(&mut region), true)
734 {
735 let (begin, end) = region.pos(0).unwrap();
736 result.push_str(&raw_regex[index..begin]);
737
738 let var_pos = region.pos(1).unwrap();
739 let var_name = &raw_regex[var_pos.0..var_pos.1];
740 let var_raw = state
741 .variables
742 .get(var_name)
743 .map(String::as_ref)
744 .unwrap_or("");
745 let var_resolved = re_resolve_variables(var_raw, state);
746 result.push_str(&var_resolved);
747
748 index = end;
749 }
750 if index < raw_regex.len() {
751 result.push_str(&raw_regex[index..]);
752 }
753 result
754}
755
756pub(crate) fn re_resolve_all_regexes(
759 syntax: &mut SyntaxDefinition,
760 lines_include_newline: bool,
761) -> Result<(), ParseSyntaxError> {
762 for context in syntax.contexts.values_mut() {
763 for pattern in &mut context.patterns {
764 if let Pattern::Match(ref mut match_pat) = pattern {
765 if let Some(ref raw) = match_pat.raw_regex_str {
766 let new_regex_str =
767 re_resolve_regex(raw, &syntax.variables, lines_include_newline)?;
768 match_pat.regex = Regex::new(new_regex_str);
769 }
770 if let MatchOperation::Embed { ref mut escape, .. } = match_pat.operation {
772 if let Some(ref raw) = escape.raw_escape_regex_str {
773 let new_regex_str =
774 re_resolve_regex(raw, &syntax.variables, lines_include_newline)?;
775 escape.escape_regex = Regex::new(new_regex_str);
776 }
777 }
778 }
779 }
780 }
781 Ok(())
782}
783
784struct ContextNamer {
785 name: String,
786 anonymous_index: Option<usize>,
787}
788
789impl ContextNamer {
790 fn new(name: &str) -> ContextNamer {
791 ContextNamer {
792 name: name.to_string(),
793 anonymous_index: None,
794 }
795 }
796
797 fn next(&mut self) -> String {
798 let name = if let Some(index) = self.anonymous_index {
799 format!("#anon_{}_{}", self.name, index)
800 } else {
801 self.name.clone()
802 };
803
804 self.anonymous_index = Some(self.anonymous_index.map(|i| i + 1).unwrap_or(0));
805 name
806 }
807}
808
809fn replace_posix_char_classes(regex: String) -> String {
814 regex
815 .replace("[:alpha:]", r"\p{L}")
816 .replace("[:alnum:]", r"\p{L}\p{N}")
817 .replace("[:lower:]", r"\p{Ll}")
818 .replace("[:upper:]", r"\p{Lu}")
819 .replace("[:digit:]", r"\p{Nd}")
820}
821
822fn regex_for_newlines(regex: String) -> String {
833 if !regex.contains('$') {
834 return regex;
835 }
836
837 let rewriter = RegexRewriterForNewlines {
838 parser: Parser::new(regex.as_bytes()),
839 };
840 rewriter.rewrite()
841}
842
843struct RegexRewriterForNewlines<'a> {
844 parser: Parser<'a>,
845}
846
847impl RegexRewriterForNewlines<'_> {
848 fn rewrite(mut self) -> String {
849 let mut result = Vec::new();
850
851 while let Some(c) = self.parser.peek() {
852 match c {
853 b'$' => {
854 self.parser.next();
855 result.extend_from_slice(br"(?m:$)");
856 }
857 b'\\' => {
858 self.parser.next();
859 result.push(c);
860 if let Some(c2) = self.parser.peek() {
861 self.parser.next();
862 result.push(c2);
863 }
864 }
865 b'[' => {
866 let (mut content, _) = self.parser.parse_character_class();
867 result.append(&mut content);
868 }
869 _ => {
870 self.parser.next();
871 result.push(c);
872 }
873 }
874 }
875 String::from_utf8(result).unwrap()
876 }
877}
878
879fn regex_for_no_newlines(regex: String) -> String {
888 if !regex.contains(r"\n") {
889 return regex;
890 }
891
892 let regex = regex.replace("(?:\\n)?", "(?:$|)");
895
896 let rewriter = RegexRewriterForNoNewlines {
897 parser: Parser::new(regex.as_bytes()),
898 };
899 rewriter.rewrite()
900}
901
902struct RegexRewriterForNoNewlines<'a> {
903 parser: Parser<'a>,
904}
905
906impl RegexRewriterForNoNewlines<'_> {
907 fn rewrite(mut self) -> String {
908 let mut result = Vec::new();
909 while let Some(c) = self.parser.peek() {
910 match c {
911 b'\\' => {
912 self.parser.next();
913 if let Some(c2) = self.parser.peek() {
914 self.parser.next();
915 let c3 = self.parser.peek();
918 if c2 == b'n' && c3 != Some(b'?') && c3 != Some(b'+') && c3 != Some(b'*') {
919 result.extend_from_slice(b"$");
920 } else {
921 result.push(c);
922 result.push(c2);
923 }
924 } else {
925 result.push(c);
926 }
927 }
928 b'[' => {
929 let (mut content, matches_newline) = self.parser.parse_character_class();
930 if matches_newline && self.parser.peek() != Some(b'?') {
931 result.extend_from_slice(b"(?:");
932 result.append(&mut content);
933 result.extend_from_slice(br"|$)");
934 } else {
935 result.append(&mut content);
936 }
937 }
938 _ => {
939 self.parser.next();
940 result.push(c);
941 }
942 }
943 }
944 String::from_utf8(result).unwrap()
945 }
946}
947
948fn get_consuming_capture_indexes(regex: &str) -> Vec<usize> {
949 let parser = ConsumingCaptureIndexParser {
950 parser: Parser::new(regex.as_bytes()),
951 };
952 parser.get_consuming_capture_indexes()
953}
954
955struct ConsumingCaptureIndexParser<'a> {
956 parser: Parser<'a>,
957}
958
959impl ConsumingCaptureIndexParser<'_> {
960 fn get_consuming_capture_indexes(mut self) -> Vec<usize> {
967 let mut result = Vec::new();
968 let mut stack = Vec::new();
969 let mut cap_num = 0;
970 let mut in_lookaround = false;
971 stack.push(in_lookaround);
972 result.push(cap_num);
973
974 while let Some(c) = self.parser.peek() {
975 match c {
976 b'\\' => {
977 self.parser.next();
978 self.parser.next();
979 }
980 b'[' => {
981 self.parser.parse_character_class();
982 }
983 b'(' => {
984 self.parser.next();
985 stack.push(in_lookaround);
987 if let Some(c2) = self.parser.peek() {
988 if c2 != b'?' {
989 cap_num += 1;
991 if !in_lookaround {
994 result.push(cap_num);
995 }
996 } else {
997 self.parser.next();
998 if let Some(c3) = self.parser.peek() {
999 self.parser.next();
1000 if c3 == b'=' || c3 == b'!' {
1001 in_lookaround = true;
1003 } else if c3 == b'<' {
1004 if let Some(c4) = self.parser.peek() {
1005 if c4 == b'=' || c4 == b'!' {
1006 self.parser.next();
1007 in_lookaround = true;
1009 }
1010 }
1011 } else if c3 == b'P' {
1012 if let Some(c4) = self.parser.peek() {
1013 if c4 == b'<' {
1014 cap_num += 1;
1016 if !in_lookaround {
1019 result.push(cap_num);
1020 }
1021 }
1022 }
1023 }
1024 }
1025 }
1026 }
1027 }
1028 b')' => {
1029 if let Some(value) = stack.pop() {
1030 in_lookaround = value;
1031 }
1032 self.parser.next();
1033 }
1034 _ => {
1035 self.parser.next();
1036 }
1037 }
1038 }
1039 result
1040 }
1041}
1042
1043struct Parser<'a> {
1044 bytes: &'a [u8],
1045 index: usize,
1046}
1047
1048impl Parser<'_> {
1049 fn new(bytes: &[u8]) -> Parser<'_> {
1050 Parser { bytes, index: 0 }
1051 }
1052
1053 fn peek(&self) -> Option<u8> {
1054 self.bytes.get(self.index).copied()
1055 }
1056
1057 fn next(&mut self) {
1058 self.index += 1;
1059 }
1060
1061 fn parse_character_class(&mut self) -> (Vec<u8>, bool) {
1062 let mut content = Vec::new();
1063 let mut negated = false;
1064 let mut nesting = 0;
1065 let mut matches_newline = false;
1066
1067 self.next();
1068 content.push(b'[');
1069 if let Some(b'^') = self.peek() {
1070 self.next();
1071 content.push(b'^');
1072 negated = true;
1073 }
1074
1075 if let Some(b']') = self.peek() {
1077 self.next();
1078 content.push(b']');
1079 }
1080
1081 while let Some(c) = self.peek() {
1082 match c {
1083 b'\\' => {
1084 self.next();
1085 content.push(c);
1086 if let Some(c2) = self.peek() {
1087 self.next();
1088 if c2 == b'n' && !negated && nesting == 0 {
1089 matches_newline = true;
1090 }
1091 content.push(c2);
1092 }
1093 }
1094 b'[' => {
1095 self.next();
1096 content.push(b'[');
1097 nesting += 1;
1098 }
1099 b']' => {
1100 self.next();
1101 content.push(b']');
1102 if nesting == 0 {
1103 break;
1104 }
1105 nesting -= 1;
1106 }
1107 _ => {
1108 self.next();
1109 content.push(c);
1110 }
1111 }
1112 }
1113
1114 (content, matches_newline)
1115 }
1116}
1117
1118#[cfg(test)]
1119mod tests {
1120 use super::*;
1121 use crate::parsing::Scope;
1122
1123 #[test]
1124 fn can_parse() {
1125 let defn: SyntaxDefinition = SyntaxDefinition::load_from_str(
1126 "name: C\nscope: source.c\ncontexts: {main: []}",
1127 false,
1128 None,
1129 )
1130 .unwrap();
1131 assert_eq!(defn.name, "C");
1132 assert_eq!(defn.scope, Scope::new("source.c").unwrap());
1133 let exts_empty: Vec<String> = Vec::new();
1134 assert_eq!(defn.file_extensions, exts_empty);
1135 assert!(!defn.hidden);
1136 assert!(defn.variables.is_empty());
1137 let defn2: SyntaxDefinition = SyntaxDefinition::load_from_str(
1138 "
1139 name: C
1140 scope: source.c
1141 file_extensions: [c, h]
1142 hidden_file_extensions: [k, l]
1143 hidden: true
1144 variables:
1145 ident: '[QY]+'
1146 contexts:
1147 prototype:
1148 - match: lol
1149 scope: source.php
1150 main:
1151 - match: \\b(if|else|for|while|{{ident}})\\b
1152 scope: keyword.control.c keyword.looping.c
1153 captures:
1154 1: meta.preprocessor.c++
1155 2: keyword.control.include.c++
1156 push: [string, 'scope:source.c#main', 'CSS.sublime-syntax#rule-list-body']
1157 with_prototype:
1158 - match: wow
1159 pop: true
1160 - match: '\"'
1161 push: string
1162 string:
1163 - meta_scope: string.quoted.double.c
1164 - meta_include_prototype: false
1165 - match: \\\\.
1166 scope: constant.character.escape.c
1167 - match: '\"'
1168 pop: true
1169 ",
1170 false,
1171 None,
1172 )
1173 .unwrap();
1174 assert_eq!(defn2.name, "C");
1175 let top_level_scope = Scope::new("source.c").unwrap();
1176 assert_eq!(defn2.scope, top_level_scope);
1177 let exts: Vec<String> = vec!["c", "h", "k", "l"]
1178 .into_iter()
1179 .map(String::from)
1180 .collect();
1181 assert_eq!(defn2.file_extensions, exts);
1182 assert!(defn2.hidden);
1183 assert_eq!(defn2.variables.get("ident").unwrap(), "[QY]+");
1184
1185 let n: Vec<Scope> = Vec::new();
1186 println!("{:?}", defn2);
1187 let main = &defn2.contexts["main"];
1189 assert_eq!(main.meta_content_scope, vec![top_level_scope]);
1190 assert_eq!(main.meta_scope, n);
1191 assert!(main.meta_include_prototype.unwrap_or(true));
1192
1193 assert_eq!(defn2.contexts["__main"].meta_content_scope, n);
1194 assert_eq!(
1195 defn2.contexts["__start"].meta_content_scope,
1196 vec![top_level_scope]
1197 );
1198
1199 assert_eq!(
1200 defn2.contexts["string"].meta_scope,
1201 vec![Scope::new("string.quoted.double.c").unwrap()]
1202 );
1203 let first_pattern: &Pattern = &main.patterns[0];
1204 match *first_pattern {
1205 Pattern::Match(ref match_pat) => {
1206 let m: &CaptureMapping = match_pat.captures.as_ref().expect("test failed");
1207 assert_eq!(
1208 &m[0],
1209 &(1, vec![Scope::new("meta.preprocessor.c++").unwrap()])
1210 );
1211 use crate::parsing::syntax_definition::ContextReference::*;
1212
1213 let expected = MatchOperation::Push(vec![
1215 Named("string".to_owned()),
1216 ByScope {
1217 scope: Scope::new("source.c").unwrap(),
1218 sub_context: Some("main".to_owned()),
1219 with_escape: false,
1220 },
1221 File {
1222 name: "CSS".to_owned(),
1223 sub_context: Some("rule-list-body".to_owned()),
1224 with_escape: false,
1225 },
1226 ]);
1227 assert_eq!(
1228 format!("{:?}", match_pat.operation),
1229 format!("{:?}", expected)
1230 );
1231
1232 assert_eq!(
1233 match_pat.scope,
1234 vec![
1235 Scope::new("keyword.control.c").unwrap(),
1236 Scope::new("keyword.looping.c").unwrap()
1237 ]
1238 );
1239
1240 assert!(match_pat.with_prototype.is_some());
1241 }
1242 _ => unreachable!(),
1243 }
1244 }
1245
1246 #[test]
1247 fn can_parse_embed_produces_embed_op() {
1248 let def = SyntaxDefinition::load_from_str(
1249 r#"
1250 name: C
1251 scope: source.c
1252 file_extensions: [c, h]
1253 variables:
1254 ident: '[QY]+'
1255 contexts:
1256 main:
1257 - match: '(>)\s*'
1258 captures:
1259 1: meta.tag.style.begin.html punctuation.definition.tag.end.html
1260 embed: scope:source.css
1261 embed_scope: source.css.embedded.html
1262 escape: (?i)(?=</style)
1263 "#,
1264 false,
1265 None,
1266 )
1267 .unwrap();
1268
1269 let main_ctx = &def.contexts["main"];
1271 if let Pattern::Match(ref match_pattern) = main_ctx.patterns[0] {
1272 match match_pattern.operation {
1273 MatchOperation::Embed {
1274 ref contexts,
1275 ref escape,
1276 pop_count,
1277 } => {
1278 assert_eq!(pop_count, 0);
1279 assert_eq!(contexts.len(), 2);
1281 assert!(matches!(contexts[0], ContextReference::Inline(_)));
1283 assert!(matches!(
1285 contexts[1],
1286 ContextReference::ByScope {
1287 with_escape: true,
1288 ..
1289 }
1290 ));
1291 assert_eq!(escape.escape_regex.regex_str(), "(?i)(?=</style)");
1293 assert!(!escape.has_captures);
1294 assert!(escape.escape_captures.is_none());
1295 }
1296 _ => panic!(
1297 "Expected Embed operation, got {:?}",
1298 match_pattern.operation
1299 ),
1300 }
1301 assert!(match_pattern.with_prototype.is_none());
1303 } else {
1304 panic!("Expected Match pattern");
1305 }
1306 }
1307
1308 #[test]
1309 fn errors_on_embed_without_escape() {
1310 let def = SyntaxDefinition::load_from_str(
1311 r#"
1312 name: C
1313 scope: source.c
1314 file_extensions: [c, h]
1315 variables:
1316 ident: '[QY]+'
1317 contexts:
1318 main:
1319 - match: '(>)\s*'
1320 captures:
1321 1: meta.tag.style.begin.html punctuation.definition.tag.end.html
1322 embed: scope:source.css
1323 embed_scope: source.css.embedded.html
1324 "#,
1325 false,
1326 None,
1327 );
1328 assert!(def.is_err());
1329 match def.unwrap_err() {
1330 ParseSyntaxError::MissingMandatoryKey(key) => assert_eq!(key, "escape"),
1331 _ => unreachable!("Got unexpected ParseSyntaxError"),
1332 }
1333 }
1334
1335 #[test]
1336 fn can_parse_pop_plus_embed() {
1337 let def = SyntaxDefinition::load_from_str(
1338 r#"
1339 name: Test
1340 scope: text.test
1341 file_extensions: [test]
1342 contexts:
1343 main:
1344 - match: '<script>'
1345 push: script-content
1346 script-content:
1347 - match: '>'
1348 pop: 1
1349 embed: scope:source.js
1350 embed_scope: source.js.embedded.html
1351 escape: '(?=</script>)'
1352 "#,
1353 false,
1354 None,
1355 )
1356 .unwrap();
1357
1358 let ctx = &def.contexts["script-content"];
1359 if let Pattern::Match(ref match_pattern) = ctx.patterns[0] {
1360 match match_pattern.operation {
1361 MatchOperation::Embed {
1362 ref contexts,
1363 ref escape,
1364 pop_count,
1365 } => {
1366 assert_eq!(pop_count, 1);
1367 assert_eq!(contexts.len(), 2);
1369 assert!(matches!(contexts[0], ContextReference::Inline(_)));
1370 assert!(matches!(
1371 contexts[1],
1372 ContextReference::ByScope {
1373 with_escape: true,
1374 ..
1375 }
1376 ));
1377 assert_eq!(escape.escape_regex.regex_str(), "(?=</script>)");
1378 }
1379 _ => panic!(
1380 "Expected Embed operation, got {:?}",
1381 match_pattern.operation
1382 ),
1383 }
1384 } else {
1385 panic!("Expected Match pattern");
1386 }
1387 }
1388
1389 #[test]
1390 fn errors_on_regex_compile_error() {
1391 let def = SyntaxDefinition::load_from_str(
1392 r#"
1393 name: C
1394 scope: source.c
1395 file_extensions: [test]
1396 contexts:
1397 main:
1398 - match: '[a'
1399 scope: keyword.name
1400 "#,
1401 false,
1402 None,
1403 );
1404 assert!(def.is_err());
1405 match def.unwrap_err() {
1406 ParseSyntaxError::RegexCompileError(ref regex, _) => assert_eq!("[a", regex),
1407 _ => unreachable!("Got unexpected ParseSyntaxError"),
1408 }
1409 }
1410
1411 #[test]
1412 fn can_parse_ugly_yaml() {
1413 let defn: SyntaxDefinition = SyntaxDefinition::load_from_str(
1414 "
1415 name: LaTeX
1416 scope: text.tex.latex
1417 contexts:
1418 main:
1419 - match: '((\\\\)(?:framebox|makebox))\\b'
1420 captures:
1421 1: support.function.box.latex
1422 2: punctuation.definition.backslash.latex
1423 push:
1424 - [{meta_scope: meta.function.box.latex}, {match: '', pop: true}]
1425 - argument
1426 - optional-arguments
1427 argument:
1428 - match: '\\{'
1429 scope: punctuation.definition.group.brace.begin.latex
1430 - match: '(?=\\S)'
1431 pop: true
1432 optional-arguments:
1433 - match: '(?=\\S)'
1434 pop: true
1435 ",
1436 false,
1437 None,
1438 )
1439 .unwrap();
1440 assert_eq!(defn.name, "LaTeX");
1441 let top_level_scope = Scope::new("text.tex.latex").unwrap();
1442 assert_eq!(defn.scope, top_level_scope);
1443
1444 let first_pattern: &Pattern = &defn.contexts["main"].patterns[0];
1445 match *first_pattern {
1446 Pattern::Match(ref match_pat) => {
1447 let m: &CaptureMapping = match_pat.captures.as_ref().expect("test failed");
1448 assert_eq!(
1449 &m[0],
1450 &(1, vec![Scope::new("support.function.box.latex").unwrap()])
1451 );
1452
1453 assert!(match_pat.with_prototype.is_none());
1459 }
1460 _ => unreachable!(),
1461 }
1462 }
1463
1464 #[test]
1465 fn names_anonymous_contexts() {
1466 let def = SyntaxDefinition::load_from_str(
1467 r#"
1468 scope: source.c
1469 contexts:
1470 main:
1471 - match: a
1472 push: a
1473 a:
1474 - meta_scope: a
1475 - match: x
1476 push:
1477 - meta_scope: anonymous_x
1478 - match: anything
1479 push:
1480 - meta_scope: anonymous_x_2
1481 - match: y
1482 push:
1483 - meta_scope: anonymous_y
1484 - match: z
1485 escape: 'test'
1486 "#,
1487 false,
1488 None,
1489 )
1490 .unwrap();
1491
1492 assert_eq!(def.contexts["a"].meta_scope, vec![Scope::new("a").unwrap()]);
1493 assert_eq!(
1494 def.contexts["#anon_a_0"].meta_scope,
1495 vec![Scope::new("anonymous_x").unwrap()]
1496 );
1497 assert_eq!(
1498 def.contexts["#anon_a_1"].meta_scope,
1499 vec![Scope::new("anonymous_x_2").unwrap()]
1500 );
1501 assert_eq!(
1502 def.contexts["#anon_a_2"].meta_scope,
1503 vec![Scope::new("anonymous_y").unwrap()]
1504 );
1505 assert!(!def.contexts.contains_key("#anon_a_3"));
1508 }
1509
1510 #[test]
1511 fn can_use_fallback_name() {
1512 let def = SyntaxDefinition::load_from_str(
1513 r#"
1514 scope: source.c
1515 contexts:
1516 main:
1517 - match: ''
1518 "#,
1519 false,
1520 Some("C"),
1521 );
1522 assert_eq!(def.unwrap().name, "C");
1523 }
1524
1525 #[test]
1526 fn can_rewrite_regex_for_newlines() {
1527 fn rewrite(s: &str) -> String {
1528 regex_for_newlines(s.to_string())
1529 }
1530
1531 assert_eq!(&rewrite(r"a"), r"a");
1532 assert_eq!(&rewrite(r"\b"), r"\b");
1533 assert_eq!(&rewrite(r"(a)"), r"(a)");
1534 assert_eq!(&rewrite(r"[a]"), r"[a]");
1535 assert_eq!(&rewrite(r"[^a]"), r"[^a]");
1536 assert_eq!(&rewrite(r"[]a]"), r"[]a]");
1537 assert_eq!(&rewrite(r"[[a]]"), r"[[a]]");
1538
1539 assert_eq!(&rewrite(r"^"), r"^");
1540 assert_eq!(&rewrite(r"$"), r"(?m:$)");
1541 assert_eq!(&rewrite(r"^ab$"), r"^ab(?m:$)");
1542 assert_eq!(&rewrite(r"\^ab\$"), r"\^ab\$");
1543 assert_eq!(&rewrite(r"(//).*$"), r"(//).*(?m:$)");
1544
1545 assert_eq!(&rewrite(r"[a$]"), r"[a$]");
1547 }
1548
1549 #[test]
1550 fn can_rewrite_regex_for_no_newlines() {
1551 fn rewrite(s: &str) -> String {
1552 regex_for_no_newlines(s.to_string())
1553 }
1554
1555 assert_eq!(&rewrite(r"a"), r"a");
1556 assert_eq!(&rewrite(r"\b"), r"\b");
1557 assert_eq!(&rewrite(r"(a)"), r"(a)");
1558 assert_eq!(&rewrite(r"[a]"), r"[a]");
1559 assert_eq!(&rewrite(r"[^a]"), r"[^a]");
1560 assert_eq!(&rewrite(r"[]a]"), r"[]a]");
1561 assert_eq!(&rewrite(r"[[a]]"), r"[[a]]");
1562
1563 assert_eq!(&rewrite(r"\n"), r"$");
1564 assert_eq!(&rewrite(r"\[\n"), r"\[$");
1565 assert_eq!(&rewrite(r"a\n?"), r"a\n?");
1566 assert_eq!(&rewrite(r"a\n+"), r"a\n+");
1567 assert_eq!(&rewrite(r"a\n*"), r"a\n*");
1568 assert_eq!(&rewrite(r"[abc\n]"), r"(?:[abc\n]|$)");
1569 assert_eq!(&rewrite(r"[^\n]"), r"[^\n]");
1570 assert_eq!(&rewrite(r"[^]\n]"), r"[^]\n]");
1571 assert_eq!(&rewrite(r"[\n]?"), r"[\n]?");
1572 assert_eq!(&rewrite(r"[\n]"), r"(?:[\n]|$)");
1574 assert_eq!(&rewrite(r"[]\n]"), r"(?:[]\n]|$)");
1575 assert_eq!(&rewrite(r"[[a]&&[\n]]"), r"[[a]&&[\n]]");
1577
1578 assert_eq!(&rewrite(r"ab(?:\n)?"), r"ab(?:$|)");
1579 assert_eq!(&rewrite(r"(?<!\n)ab"), r"(?<!$)ab");
1580 assert_eq!(&rewrite(r"(?<=\n)ab"), r"(?<=$)ab");
1581 }
1582
1583 #[test]
1584 fn can_get_valid_captures_from_regex() {
1585 let regex = "hello(test)(?=(world))(foo(?P<named>bar))";
1586 println!("{:?}", regex);
1587 let valid_indexes = get_consuming_capture_indexes(regex);
1588 println!("{:?}", valid_indexes);
1589 assert_eq!(valid_indexes, [0, 1, 3, 4]);
1590 }
1591
1592 #[test]
1593 fn can_get_valid_captures_from_regex2() {
1594 let regex = "hello(test)[(?=tricked](foo(bar))";
1595 println!("{:?}", regex);
1596 let valid_indexes = get_consuming_capture_indexes(regex);
1597 println!("{:?}", valid_indexes);
1598 assert_eq!(valid_indexes, [0, 1, 2, 3]);
1599 }
1600
1601 #[test]
1602 fn can_get_valid_captures_from_nested_regex() {
1603 let regex = "hello(test)(?=(world(?!(te(?<=(st))))))(foo(bar))";
1604 println!("{:?}", regex);
1605 let valid_indexes = get_consuming_capture_indexes(regex);
1606 println!("{:?}", valid_indexes);
1607 assert_eq!(valid_indexes, [0, 1, 5, 6]);
1608 }
1609
1610 #[test]
1611 fn error_loading_syntax_with_unescaped_backslash() {
1612 let load_err = SyntaxDefinition::load_from_str(
1613 r#"
1614 name: Unescaped Backslash
1615 scope: source.c
1616 file_extensions: [test]
1617 contexts:
1618 main:
1619 - match: '\'
1620 "#,
1621 false,
1622 None,
1623 )
1624 .unwrap_err();
1625 match load_err {
1626 ParseSyntaxError::RegexCompileError(bad_regex, _) => assert_eq!(bad_regex, r"\"),
1627 _ => panic!("Unexpected error: {load_err}"),
1628 }
1629 }
1630
1631 #[test]
1632 fn can_parse_extends_field() {
1633 let defn = SyntaxDefinition::load_from_str(
1634 r#"
1635 name: C++
1636 scope: source.c++
1637 extends: Packages/C/C.sublime-syntax
1638 contexts:
1639 main:
1640 - match: 'class'
1641 scope: keyword.c++
1642 "#,
1643 false,
1644 None,
1645 )
1646 .unwrap();
1647 assert_eq!(defn.extends, vec!["Packages/C/C.sublime-syntax".to_owned()]);
1648 }
1649
1650 #[test]
1651 fn can_parse_extends_without_main() {
1652 let defn = SyntaxDefinition::load_from_str(
1654 r#"
1655 name: C++ Extra
1656 scope: source.c++
1657 extends: Packages/C/C.sublime-syntax
1658 contexts:
1659 extra:
1660 - match: 'extra'
1661 scope: keyword.extra
1662 "#,
1663 false,
1664 None,
1665 )
1666 .unwrap();
1667 assert_eq!(defn.extends, vec!["Packages/C/C.sublime-syntax".to_owned()]);
1668 assert!(!defn.contexts.contains_key("main"));
1669 }
1670
1671 #[test]
1672 fn can_parse_version_field() {
1673 let defn = SyntaxDefinition::load_from_str(
1674 r#"
1675 name: V2 Test
1676 scope: source.v2
1677 version: 2
1678 contexts:
1679 main:
1680 - match: 'test'
1681 scope: keyword.test
1682 "#,
1683 false,
1684 None,
1685 )
1686 .unwrap();
1687 assert_eq!(defn.version, 2);
1688 }
1689
1690 #[test]
1691 fn version_defaults_to_1() {
1692 let defn = SyntaxDefinition::load_from_str(
1693 "name: V1\nscope: source.v1\ncontexts: {main: []}",
1694 false,
1695 None,
1696 )
1697 .unwrap();
1698 assert_eq!(defn.version, 1);
1699 }
1700
1701 #[test]
1702 fn can_parse_meta_prepend() {
1703 let defn = SyntaxDefinition::load_from_str(
1704 r#"
1705 name: Test
1706 scope: source.test
1707 extends: Packages/Base/Base.sublime-syntax
1708 contexts:
1709 main:
1710 - meta_prepend: true
1711 - match: 'prepended'
1712 scope: keyword.prepended
1713 "#,
1714 false,
1715 None,
1716 )
1717 .unwrap();
1718 let main = &defn.contexts["main"];
1719 assert_eq!(main.merge_mode, ContextMergeMode::Prepend);
1720 }
1721
1722 #[test]
1723 fn can_parse_meta_append() {
1724 let defn = SyntaxDefinition::load_from_str(
1725 r#"
1726 name: Test
1727 scope: source.test
1728 extends: Packages/Base/Base.sublime-syntax
1729 contexts:
1730 main:
1731 - meta_append: true
1732 - match: 'appended'
1733 scope: keyword.appended
1734 "#,
1735 false,
1736 None,
1737 )
1738 .unwrap();
1739 let main = &defn.contexts["main"];
1740 assert_eq!(main.merge_mode, ContextMergeMode::Append);
1741 }
1742
1743 #[test]
1744 fn can_parse_apply_prototype() {
1745 let defn = SyntaxDefinition::load_from_str(
1746 r#"
1747 name: Test
1748 scope: source.test
1749 contexts:
1750 main:
1751 - include: scope:source.other
1752 apply_prototype: true
1753 "#,
1754 false,
1755 None,
1756 )
1757 .unwrap();
1758 let main = &defn.contexts["main"];
1759 assert_eq!(main.patterns.len(), 1);
1760 match &main.patterns[0] {
1761 Pattern::IncludeWithPrototype(_) => {}
1762 other => panic!("Expected IncludeWithPrototype, got {:?}", other),
1763 }
1764 }
1765
1766 #[test]
1767 fn stores_raw_regex_str() {
1768 let defn = SyntaxDefinition::load_from_str(
1769 r#"
1770 name: Test
1771 scope: source.test
1772 variables:
1773 ident: '[a-z]+'
1774 contexts:
1775 main:
1776 - match: '{{ident}}'
1777 scope: variable.test
1778 "#,
1779 false,
1780 None,
1781 )
1782 .unwrap();
1783 let main = &defn.contexts["main"];
1784 match &main.patterns[0] {
1785 Pattern::Match(mp) => {
1786 assert_eq!(mp.raw_regex_str.as_deref(), Some("{{ident}}"));
1787 }
1788 _ => panic!("Expected Match pattern"),
1789 }
1790 }
1791}