textmate_lang/rule.rs
1//! The rules of a TextMate grammar: matches, begin/end and begin/while
2//! regions, includes, and pattern lists.
3
4use alloc::{borrow::Cow, vec::Vec};
5
6/// Text held by the model: borrowed from a `&'static str`, or owned.
7pub(crate) type Text = Cow<'static, str>;
8
9/// Captures: group numbers and what to do with the captured text.
10pub(crate) type Captures = Vec<(u32, Capture)>;
11
12/// One entry of a pattern list: what the tokenizer tries at each position.
13///
14/// A rule is one of:
15///
16/// - a [`Match`]: one regular expression, scoped as a whole and by capture
17/// group (`"match"` in the JSON);
18/// - a [`BeginEnd`]: a region from a `begin` match to an `end` match, with
19/// its own patterns inside (`"begin"`/`"end"`);
20/// - a [`BeginWhile`]: a region that starts at a `begin` match and continues
21/// on each following line while a `while` pattern matches at its start
22/// (`"begin"`/`"while"`), as Markdown block quotes do;
23/// - an include of a repository entry, the grammar itself, the base grammar,
24/// or another grammar (`"include"`);
25/// - a list of rules tried together ([`Rule::patterns`]).
26///
27/// [`Match`], [`BeginEnd`], and [`BeginWhile`] convert into a `Rule` with
28/// `into()`, and every method that takes a rule accepts them directly.
29///
30/// # Examples
31///
32/// ```
33/// use textmate_lang::{BeginEnd, Grammar, Match, Rule};
34///
35/// let json = Grammar::new("source.demo")
36/// .pattern(Rule::include("comments"))
37/// .pattern(Match::new(r"\b(let|fn)\b").scope("keyword.other.demo"))
38/// .repository(
39/// "comments",
40/// Rule::patterns([
41/// Match::new("//.*$").scope("comment.line.double-slash.demo").into(),
42/// BeginEnd::new(r"/\*", r"\*/").scope("comment.block.demo").into(),
43/// ]),
44/// )
45/// .to_json()?;
46/// assert!(json.contains(r##""include": "#comments""##));
47/// # Ok::<(), textmate_lang::Error>(())
48/// ```
49#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct Rule(pub(crate) Kind);
51
52/// What a [`Rule`] is.
53#[derive(Clone, Debug, PartialEq, Eq)]
54pub(crate) enum Kind {
55 Match(Match),
56 BeginEnd(BeginEnd),
57 BeginWhile(BeginWhile),
58 Include(Include),
59 Patterns(Vec<Rule>),
60}
61
62/// The target of an include.
63#[derive(Clone, Debug, PartialEq, Eq)]
64pub(crate) enum Include {
65 /// `#name`: an entry of this grammar's repository.
66 Repository(Text),
67 /// `$self`: this grammar's top-level patterns.
68 SelfRef,
69 /// `$base`: the top-level patterns of the grammar being highlighted,
70 /// which is this one unless it is embedded in another.
71 Base,
72 /// `scope`: another grammar's top-level patterns.
73 Scope(Text),
74 /// `scope#name`: an entry of another grammar's repository.
75 ScopeEntry(Text, Text),
76}
77
78impl Rule {
79 /// Includes the repository entry called `name` (`"include": "#name"`).
80 ///
81 /// The entry must exist ([`Error::UndefinedInclude`](crate::Error::UndefinedInclude)),
82 /// checked when the grammar is emitted. The entry's patterns are tried as
83 /// if they were written in place of the include.
84 ///
85 /// | Parameter | Meaning |
86 /// |---|---|
87 /// | `name` | A repository entry's name, without the `#`. |
88 ///
89 /// # Examples
90 ///
91 /// ```
92 /// use textmate_lang::{Grammar, Match, Rule};
93 ///
94 /// let json = Grammar::new("source.demo")
95 /// .pattern(Rule::include("numbers"))
96 /// .repository("numbers", Match::new(r"\d+").scope("constant.numeric.demo"))
97 /// .to_json()?;
98 /// assert!(json.contains(r##""include": "#numbers""##));
99 /// # Ok::<(), textmate_lang::Error>(())
100 /// ```
101 #[must_use]
102 pub fn include(name: impl Into<Text>) -> Rule {
103 Rule(Kind::Include(Include::Repository(name.into())))
104 }
105
106 /// Includes this grammar's top-level patterns (`"include": "$self"`),
107 /// typically inside a region whose contents are the language again, such
108 /// as the body of a block.
109 ///
110 /// # Examples
111 ///
112 /// ```
113 /// use textmate_lang::{BeginEnd, Grammar, Rule};
114 ///
115 /// let json = Grammar::new("source.demo")
116 /// .pattern(BeginEnd::new(r"\{", r"\}").scope("meta.block.demo").pattern(Rule::include_self()))
117 /// .to_json()?;
118 /// assert!(json.contains(r#""include": "$self""#));
119 /// # Ok::<(), textmate_lang::Error>(())
120 /// ```
121 #[must_use]
122 pub fn include_self() -> Rule {
123 Rule(Kind::Include(Include::SelfRef))
124 }
125
126 /// Includes the base grammar's top-level patterns (`"include": "$base"`):
127 /// the grammar of the file being highlighted. That is this grammar,
128 /// unless this one is embedded in another (a SQL grammar inside a host
129 /// language's strings, say), in which case it is the host.
130 ///
131 /// # Examples
132 ///
133 /// ```
134 /// use textmate_lang::{BeginEnd, Grammar, Rule};
135 ///
136 /// let json = Grammar::new("source.demo")
137 /// .pattern(BeginEnd::new(r"\(", r"\)").pattern(Rule::include_base()))
138 /// .to_json()?;
139 /// assert!(json.contains(r#""include": "$base""#));
140 /// # Ok::<(), textmate_lang::Error>(())
141 /// ```
142 #[must_use]
143 pub fn include_base() -> Rule {
144 Rule(Kind::Include(Include::Base))
145 }
146
147 /// Includes another grammar's top-level patterns by its scope name
148 /// (`"include": "source.js"`), to embed one language in another.
149 ///
150 /// The other grammar is not available here, so only the scope name's
151 /// syntax is checked. Naming this grammar's own scope is the same as
152 /// [`include_self`](Rule::include_self).
153 ///
154 /// | Parameter | Meaning |
155 /// |---|---|
156 /// | `scope` | The other grammar's `scopeName`. |
157 ///
158 /// # Examples
159 ///
160 /// ```
161 /// use textmate_lang::{BeginEnd, Grammar, Rule};
162 ///
163 /// let json = Grammar::new("text.html.demo")
164 /// .pattern(
165 /// BeginEnd::new("<script>", "</script>")
166 /// .content_scope("source.js.embedded.demo")
167 /// .pattern(Rule::include_scope("source.js")),
168 /// )
169 /// .to_json()?;
170 /// assert!(json.contains(r#""include": "source.js""#));
171 /// # Ok::<(), textmate_lang::Error>(())
172 /// ```
173 #[must_use]
174 pub fn include_scope(scope: impl Into<Text>) -> Rule {
175 Rule(Kind::Include(Include::Scope(scope.into())))
176 }
177
178 /// Includes one repository entry of another grammar
179 /// (`"include": "source.js#expression"`).
180 ///
181 /// Only the syntax is checked, unless `scope` is this grammar's own
182 /// scope name: then the entry must exist, as for
183 /// [`include`](Rule::include).
184 ///
185 /// | Parameter | Meaning |
186 /// |---|---|
187 /// | `scope` | The other grammar's `scopeName`. |
188 /// | `name` | The entry in that grammar's repository. |
189 ///
190 /// # Examples
191 ///
192 /// ```
193 /// use textmate_lang::{Grammar, Rule};
194 ///
195 /// let json = Grammar::new("source.demo")
196 /// .pattern(Rule::include_scope_entry("source.js", "expression"))
197 /// .to_json()?;
198 /// assert!(json.contains(r#""include": "source.js#expression""#));
199 /// # Ok::<(), textmate_lang::Error>(())
200 /// ```
201 #[must_use]
202 pub fn include_scope_entry(scope: impl Into<Text>, name: impl Into<Text>) -> Rule {
203 Rule(Kind::Include(Include::ScopeEntry(
204 scope.into(),
205 name.into(),
206 )))
207 }
208
209 /// A list of rules tried together, as one rule (`{"patterns": [...]}`).
210 ///
211 /// The usual body of a repository entry that groups related rules, so
212 /// that one include pulls in all of them. The rules are tried in order;
213 /// at each position the earliest match wins, and the first rule in the
214 /// list wins a tie.
215 ///
216 /// | Parameter | Meaning |
217 /// |---|---|
218 /// | `rules` | The rules, in priority order. |
219 ///
220 /// # Examples
221 ///
222 /// ```
223 /// use textmate_lang::{Grammar, Match, Rule};
224 ///
225 /// let literals = Rule::patterns([
226 /// Match::new(r"\b(true|false)\b").scope("constant.language.demo").into(),
227 /// Match::new(r"\b\d+\b").scope("constant.numeric.demo").into(),
228 /// ]);
229 /// let json = Grammar::new("source.demo")
230 /// .pattern(Rule::include("literals"))
231 /// .repository("literals", literals)
232 /// .to_json_compact()?;
233 /// assert!(json.contains(r#""literals":{"patterns":[{"name":"constant.language.demo""#));
234 /// # Ok::<(), textmate_lang::Error>(())
235 /// ```
236 #[must_use]
237 pub fn patterns(rules: impl IntoIterator<Item = Rule>) -> Rule {
238 Rule(Kind::Patterns(rules.into_iter().collect()))
239 }
240}
241
242impl Drop for Rule {
243 /// Drops nested rules from an explicit stack, so dropping a rule nested
244 /// arbitrarily deep cannot overflow the call stack.
245 fn drop(&mut self) {
246 let mut stack = Vec::new();
247 self.0.take_children(&mut stack);
248 while let Some(mut rule) = stack.pop() {
249 rule.0.take_children(&mut stack);
250 }
251 }
252}
253
254impl Kind {
255 /// Moves every directly nested rule into `out`.
256 fn take_children(&mut self, out: &mut Vec<Rule>) {
257 fn captures(list: &mut Captures, out: &mut Vec<Rule>) {
258 for (_, capture) in list.iter_mut() {
259 out.append(&mut capture.patterns);
260 }
261 }
262 match self {
263 Kind::Match(m) => captures(&mut m.captures, out),
264 Kind::BeginEnd(r) => {
265 out.append(&mut r.patterns);
266 captures(&mut r.captures, out);
267 captures(&mut r.begin_captures, out);
268 captures(&mut r.end_captures, out);
269 }
270 Kind::BeginWhile(r) => {
271 out.append(&mut r.patterns);
272 captures(&mut r.captures, out);
273 captures(&mut r.begin_captures, out);
274 captures(&mut r.while_captures, out);
275 }
276 Kind::Patterns(rules) => out.append(rules),
277 Kind::Include(_) => {}
278 }
279 }
280}
281
282/// What to do with the text a capture group matched: give it a scope, run
283/// patterns over it, or both.
284///
285/// A scope name converts into a `Capture` directly, so the common case is
286/// `.capture(1, "entity.name.function.demo")`.
287///
288/// # Examples
289///
290/// ```
291/// use textmate_lang::{Capture, Grammar, Match, Rule};
292///
293/// let json = Grammar::new("source.demo")
294/// .pattern(
295/// Match::new(r#"(import)\s+("[^"]*")"#)
296/// .capture(1, "keyword.control.import.demo")
297/// .capture(2, Capture::new().scope("string.quoted.demo").pattern(Rule::include("escapes"))),
298/// )
299/// .repository("escapes", textmate_lang::Match::new(r"\\.").scope("constant.character.escape.demo"))
300/// .to_json()?;
301/// assert!(json.contains(r#""2": {"#));
302/// # Ok::<(), textmate_lang::Error>(())
303/// ```
304#[derive(Clone, Debug, Default, PartialEq, Eq)]
305pub struct Capture {
306 pub(crate) scope: Option<Text>,
307 pub(crate) patterns: Vec<Rule>,
308}
309
310impl Capture {
311 /// A capture that does nothing until given a scope or patterns.
312 ///
313 /// # Examples
314 ///
315 /// ```
316 /// use textmate_lang::Capture;
317 ///
318 /// let capture = Capture::new().scope("variable.parameter.demo");
319 /// assert_eq!(capture, Capture::from("variable.parameter.demo"));
320 /// ```
321 #[must_use]
322 pub fn new() -> Capture {
323 Capture::default()
324 }
325
326 /// Scopes the captured text (`"name"`). May substitute captured text
327 /// with `$n`, `${n:/downcase}`, or `${n:/upcase}`.
328 ///
329 /// | Parameter | Meaning |
330 /// |---|---|
331 /// | `scope` | One scope name, or several separated by single spaces. |
332 ///
333 /// # Examples
334 ///
335 /// ```
336 /// use textmate_lang::{Capture, Grammar, Match};
337 ///
338 /// let json = Grammar::new("source.demo")
339 /// .pattern(Match::new(r"(\w+)!").capture(1, Capture::new().scope("support.function.$1.demo")))
340 /// .to_json()?;
341 /// assert!(json.contains("support.function.$1.demo"));
342 /// # Ok::<(), textmate_lang::Error>(())
343 /// ```
344 #[must_use]
345 pub fn scope(mut self, scope: impl Into<Text>) -> Capture {
346 self.scope = Some(scope.into());
347 self
348 }
349
350 /// Adds a rule run over the captured text (`"patterns"`), to highlight
351 /// inside it.
352 ///
353 /// | Parameter | Meaning |
354 /// |---|---|
355 /// | `rule` | The rule to add. |
356 ///
357 /// # Examples
358 ///
359 /// ```
360 /// use textmate_lang::{Capture, Grammar, Match};
361 ///
362 /// let json = Grammar::new("source.demo")
363 /// .pattern(
364 /// Match::new(r"@(\w+(?:\.\w+)*)")
365 /// .capture(1, Capture::new().pattern(Match::new(r"\.").scope("punctuation.separator.demo"))),
366 /// )
367 /// .to_json()?;
368 /// assert!(json.contains("punctuation.separator.demo"));
369 /// # Ok::<(), textmate_lang::Error>(())
370 /// ```
371 #[must_use]
372 pub fn pattern(mut self, rule: impl Into<Rule>) -> Capture {
373 self.patterns.push(rule.into());
374 self
375 }
376}
377
378impl From<&'static str> for Capture {
379 fn from(scope: &'static str) -> Capture {
380 Capture::new().scope(scope)
381 }
382}
383
384impl From<alloc::string::String> for Capture {
385 fn from(scope: alloc::string::String) -> Capture {
386 Capture::new().scope(scope)
387 }
388}
389
390impl From<Text> for Capture {
391 fn from(scope: Text) -> Capture {
392 Capture::new().scope(scope)
393 }
394}
395
396/// A rule that matches one regular expression (`"match"`).
397///
398/// The whole match takes the rule's [`scope`](Match::scope); capture groups
399/// take their own with [`capture`](Match::capture). Group 0 is the whole
400/// match.
401///
402/// # Examples
403///
404/// ```
405/// use textmate_lang::{Grammar, Match};
406///
407/// let json = Grammar::new("source.demo")
408/// .pattern(
409/// Match::new(r"\b(fn)\s+([a-z_]\w*)")
410/// .capture(1, "storage.type.function.demo")
411/// .capture(2, "entity.name.function.demo"),
412/// )
413/// .to_json()?;
414/// assert!(json.contains(r#""match": "\\b(fn)\\s+([a-z_]\\w*)""#));
415/// # Ok::<(), textmate_lang::Error>(())
416/// ```
417#[derive(Clone, Debug, PartialEq, Eq)]
418pub struct Match {
419 pub(crate) regex: Text,
420 pub(crate) scope: Option<Text>,
421 pub(crate) captures: Captures,
422}
423
424impl Match {
425 /// A rule matching `regex`, an Oniguruma regular expression in the
426 /// [supported subset](crate::validate_regex).
427 ///
428 /// | Parameter | Meaning |
429 /// |---|---|
430 /// | `regex` | The pattern. Must be valid and non-empty, checked on emission. |
431 ///
432 /// # Examples
433 ///
434 /// ```
435 /// use textmate_lang::{Error, Grammar, Match, RegexErrorKind};
436 ///
437 /// let err = Grammar::new("source.demo").pattern(Match::new("(unclosed")).to_json().unwrap_err();
438 /// match err {
439 /// Error::InvalidRegex { path, error } => {
440 /// assert_eq!(path, "/patterns/0/match");
441 /// assert_eq!(error.kind, RegexErrorKind::UnclosedGroup);
442 /// }
443 /// other => panic!("{other}"),
444 /// }
445 /// ```
446 #[must_use]
447 pub fn new(regex: impl Into<Text>) -> Match {
448 Match {
449 regex: regex.into(),
450 scope: None,
451 captures: Vec::new(),
452 }
453 }
454
455 /// Scopes the whole match (`"name"`). May substitute captured text with
456 /// `$n`, `${n:/downcase}`, or `${n:/upcase}`.
457 ///
458 /// | Parameter | Meaning |
459 /// |---|---|
460 /// | `scope` | One scope name, or several separated by single spaces. |
461 ///
462 /// # Examples
463 ///
464 /// ```
465 /// use textmate_lang::{Grammar, Match};
466 ///
467 /// let json = Grammar::new("source.demo")
468 /// .pattern(Match::new(r"\b(if|else)\b").scope("keyword.control.$1.demo"))
469 /// .to_json()?;
470 /// assert!(json.contains(r#""name": "keyword.control.$1.demo""#));
471 /// # Ok::<(), textmate_lang::Error>(())
472 /// ```
473 #[must_use]
474 pub fn scope(mut self, scope: impl Into<Text>) -> Match {
475 self.scope = Some(scope.into());
476 self
477 }
478
479 /// Sets what to do with capture group `group` (`"captures"`).
480 ///
481 /// The group must exist in the pattern
482 /// ([`Error::CaptureOutOfRange`](crate::Error::CaptureOutOfRange)), and
483 /// may be set once ([`Error::DuplicateCapture`](crate::Error::DuplicateCapture)).
484 ///
485 /// | Parameter | Meaning |
486 /// |---|---|
487 /// | `group` | The group number; 0 is the whole match. |
488 /// | `capture` | A scope name, or a [`Capture`]. |
489 ///
490 /// # Examples
491 ///
492 /// ```
493 /// use textmate_lang::{Grammar, Match};
494 ///
495 /// let json = Grammar::new("source.demo")
496 /// .pattern(Match::new(r"(\w+)\s*(=)").capture(2, "keyword.operator.demo").capture(1, "variable.demo"))
497 /// .to_json_compact()?;
498 /// // Captures are emitted in group order.
499 /// assert!(json.contains(r#""captures":{"1":{"name":"variable.demo"},"2":"#));
500 /// # Ok::<(), textmate_lang::Error>(())
501 /// ```
502 #[must_use]
503 pub fn capture(mut self, group: u32, capture: impl Into<Capture>) -> Match {
504 self.captures.push((group, capture.into()));
505 self
506 }
507}
508
509/// A region from a `begin` match to an `end` match (`"begin"`/`"end"`).
510///
511/// Between the two, the region's own [`pattern`](BeginEnd::pattern)s apply
512/// instead of the surrounding ones. Numbered back-references in `end`
513/// (`\1`) stand for the text the `begin` groups captured, which is how a
514/// heredoc or a raw string ends with its opening delimiter.
515///
516/// # Examples
517///
518/// ```
519/// use textmate_lang::{BeginEnd, Grammar, Match};
520///
521/// let string = BeginEnd::new("\"", "\"")
522/// .scope("string.quoted.double.demo")
523/// .begin_capture(0, "punctuation.definition.string.begin.demo")
524/// .end_capture(0, "punctuation.definition.string.end.demo")
525/// .pattern(Match::new(r"\\.").scope("constant.character.escape.demo"));
526/// let raw = BeginEnd::new(r#"r(#*)""#, r#""\1"#).scope("string.quoted.raw.demo");
527/// let json = Grammar::new("source.demo").pattern(string).pattern(raw).to_json()?;
528/// assert!(json.contains(r#""end": "\"\\1""#));
529/// # Ok::<(), textmate_lang::Error>(())
530/// ```
531#[derive(Clone, Debug, PartialEq, Eq)]
532pub struct BeginEnd {
533 pub(crate) begin: Text,
534 pub(crate) end: Text,
535 pub(crate) scope: Option<Text>,
536 pub(crate) content_scope: Option<Text>,
537 pub(crate) captures: Captures,
538 pub(crate) begin_captures: Captures,
539 pub(crate) end_captures: Captures,
540 pub(crate) patterns: Vec<Rule>,
541 pub(crate) apply_end_pattern_last: bool,
542}
543
544impl BeginEnd {
545 /// A region from a match of `begin` to the next match of `end`.
546 ///
547 /// | Parameter | Meaning |
548 /// |---|---|
549 /// | `begin` | The pattern that opens the region. |
550 /// | `end` | The pattern that closes it; `\n` back-references name `begin` groups. |
551 ///
552 /// # Examples
553 ///
554 /// ```
555 /// use textmate_lang::{BeginEnd, Grammar};
556 ///
557 /// let json = Grammar::new("source.demo")
558 /// .pattern(BeginEnd::new(r"/\*", r"\*/").scope("comment.block.demo"))
559 /// .to_json()?;
560 /// assert!(json.contains(r#""begin": "/\\*""#));
561 /// # Ok::<(), textmate_lang::Error>(())
562 /// ```
563 #[must_use]
564 pub fn new(begin: impl Into<Text>, end: impl Into<Text>) -> BeginEnd {
565 BeginEnd {
566 begin: begin.into(),
567 end: end.into(),
568 scope: None,
569 content_scope: None,
570 captures: Vec::new(),
571 begin_captures: Vec::new(),
572 end_captures: Vec::new(),
573 patterns: Vec::new(),
574 apply_end_pattern_last: false,
575 }
576 }
577
578 /// Scopes the whole region, delimiters included (`"name"`). `$n`
579 /// substitutions name `begin` groups.
580 ///
581 /// # Examples
582 ///
583 /// ```
584 /// use textmate_lang::{BeginEnd, Grammar};
585 ///
586 /// let json = Grammar::new("source.demo")
587 /// .pattern(BeginEnd::new(r"\(", r"\)").scope("meta.parens.demo"))
588 /// .to_json()?;
589 /// assert!(json.contains("meta.parens.demo"));
590 /// # Ok::<(), textmate_lang::Error>(())
591 /// ```
592 #[must_use]
593 pub fn scope(mut self, scope: impl Into<Text>) -> BeginEnd {
594 self.scope = Some(scope.into());
595 self
596 }
597
598 /// Scopes the text between the delimiters only (`"contentName"`). `$n`
599 /// substitutions name `begin` groups.
600 ///
601 /// # Examples
602 ///
603 /// ```
604 /// use textmate_lang::{BeginEnd, Grammar};
605 ///
606 /// let json = Grammar::new("source.demo")
607 /// .pattern(BeginEnd::new("```(\\w+)", "```").content_scope("meta.embedded.block.$1"))
608 /// .to_json()?;
609 /// assert!(json.contains(r#""contentName": "meta.embedded.block.$1""#));
610 /// # Ok::<(), textmate_lang::Error>(())
611 /// ```
612 #[must_use]
613 pub fn content_scope(mut self, scope: impl Into<Text>) -> BeginEnd {
614 self.content_scope = Some(scope.into());
615 self
616 }
617
618 /// Sets capture `group` for both `begin` and `end` (`"captures"`).
619 /// Ignored for a side that has its own captures; the group must exist in
620 /// each pattern it applies to.
621 ///
622 /// # Examples
623 ///
624 /// ```
625 /// use textmate_lang::{BeginEnd, Grammar};
626 ///
627 /// let json = Grammar::new("source.demo")
628 /// .pattern(BeginEnd::new(r"\[", r"\]").capture(0, "punctuation.brackets.demo"))
629 /// .to_json()?;
630 /// assert!(json.contains(r#""captures": {"#));
631 /// # Ok::<(), textmate_lang::Error>(())
632 /// ```
633 #[must_use]
634 pub fn capture(mut self, group: u32, capture: impl Into<Capture>) -> BeginEnd {
635 self.captures.push((group, capture.into()));
636 self
637 }
638
639 /// Sets capture `group` of `begin` (`"beginCaptures"`).
640 ///
641 /// # Examples
642 ///
643 /// ```
644 /// use textmate_lang::{BeginEnd, Grammar};
645 ///
646 /// let json = Grammar::new("source.demo")
647 /// .pattern(BeginEnd::new(r"(<)(\w+)", "/>").begin_capture(2, "entity.name.tag.demo"))
648 /// .to_json()?;
649 /// assert!(json.contains(r#""beginCaptures": {"#));
650 /// # Ok::<(), textmate_lang::Error>(())
651 /// ```
652 #[must_use]
653 pub fn begin_capture(mut self, group: u32, capture: impl Into<Capture>) -> BeginEnd {
654 self.begin_captures.push((group, capture.into()));
655 self
656 }
657
658 /// Sets capture `group` of `end` (`"endCaptures"`).
659 ///
660 /// # Examples
661 ///
662 /// ```
663 /// use textmate_lang::{BeginEnd, Grammar};
664 ///
665 /// let json = Grammar::new("source.demo")
666 /// .pattern(BeginEnd::new("<<", "(>>)").end_capture(1, "punctuation.demo"))
667 /// .to_json()?;
668 /// assert!(json.contains(r#""endCaptures": {"#));
669 /// # Ok::<(), textmate_lang::Error>(())
670 /// ```
671 #[must_use]
672 pub fn end_capture(mut self, group: u32, capture: impl Into<Capture>) -> BeginEnd {
673 self.end_captures.push((group, capture.into()));
674 self
675 }
676
677 /// Adds a rule that applies inside the region (`"patterns"`).
678 ///
679 /// # Examples
680 ///
681 /// ```
682 /// use textmate_lang::{BeginEnd, Grammar, Rule};
683 ///
684 /// let json = Grammar::new("source.demo")
685 /// .pattern(BeginEnd::new(r"\{", r"\}").pattern(Rule::include_self()))
686 /// .to_json()?;
687 /// assert!(json.contains(r#""patterns": ["#));
688 /// # Ok::<(), textmate_lang::Error>(())
689 /// ```
690 #[must_use]
691 pub fn pattern(mut self, rule: impl Into<Rule>) -> BeginEnd {
692 self.patterns.push(rule.into());
693 self
694 }
695
696 /// Whether `end` is tried after the region's patterns rather than before
697 /// (`"applyEndPatternLast": 1`), so a pattern that matches at the same
698 /// position as `end` wins.
699 ///
700 /// # Examples
701 ///
702 /// ```
703 /// use textmate_lang::{BeginEnd, Grammar};
704 ///
705 /// let json = Grammar::new("source.demo")
706 /// .pattern(BeginEnd::new("<", ">").apply_end_pattern_last(true))
707 /// .to_json()?;
708 /// assert!(json.contains(r#""applyEndPatternLast": 1"#));
709 /// # Ok::<(), textmate_lang::Error>(())
710 /// ```
711 #[must_use]
712 pub fn apply_end_pattern_last(mut self, last: bool) -> BeginEnd {
713 self.apply_end_pattern_last = last;
714 self
715 }
716}
717
718/// A region that opens at a `begin` match and continues, line by line, for
719/// as long as a `while` pattern matches at the start of each following line
720/// (`"begin"`/`"while"`), as Markdown block quotes and lists do.
721///
722/// Numbered back-references in `while` stand for the text `begin` groups
723/// captured.
724///
725/// # Examples
726///
727/// ```
728/// use textmate_lang::{BeginWhile, Grammar, Rule};
729///
730/// let quote = BeginWhile::new(r"^\s*(>) ?", r"^\s*(>) ?")
731/// .scope("markup.quote.demo")
732/// .begin_capture(1, "punctuation.definition.quote.demo")
733/// .while_capture(1, "punctuation.definition.quote.demo")
734/// .pattern(Rule::include_self());
735/// let json = Grammar::new("text.demo").pattern(quote).to_json()?;
736/// assert!(json.contains(r#""while": "^\\s*(>) ?""#));
737/// # Ok::<(), textmate_lang::Error>(())
738/// ```
739#[derive(Clone, Debug, PartialEq, Eq)]
740pub struct BeginWhile {
741 pub(crate) begin: Text,
742 pub(crate) while_: Text,
743 pub(crate) scope: Option<Text>,
744 pub(crate) content_scope: Option<Text>,
745 pub(crate) captures: Captures,
746 pub(crate) begin_captures: Captures,
747 pub(crate) while_captures: Captures,
748 pub(crate) patterns: Vec<Rule>,
749}
750
751impl BeginWhile {
752 /// A region opened by `begin` and continued while `while_` matches.
753 ///
754 /// | Parameter | Meaning |
755 /// |---|---|
756 /// | `begin` | The pattern that opens the region. |
757 /// | `while_` | The pattern each following line must start with; `\n` back-references name `begin` groups. |
758 ///
759 /// # Examples
760 ///
761 /// ```
762 /// use textmate_lang::{BeginWhile, Grammar};
763 ///
764 /// let json = Grammar::new("text.demo")
765 /// .pattern(BeginWhile::new("^(\\s{4})", "^\\1").scope("markup.raw.block.demo"))
766 /// .to_json()?;
767 /// assert!(json.contains(r#""while": "^\\1""#));
768 /// # Ok::<(), textmate_lang::Error>(())
769 /// ```
770 #[must_use]
771 pub fn new(begin: impl Into<Text>, while_: impl Into<Text>) -> BeginWhile {
772 BeginWhile {
773 begin: begin.into(),
774 while_: while_.into(),
775 scope: None,
776 content_scope: None,
777 captures: Vec::new(),
778 begin_captures: Vec::new(),
779 while_captures: Vec::new(),
780 patterns: Vec::new(),
781 }
782 }
783
784 /// Scopes the whole region (`"name"`). `$n` substitutions name `begin`
785 /// groups.
786 ///
787 /// # Examples
788 ///
789 /// ```
790 /// use textmate_lang::{BeginWhile, Grammar};
791 ///
792 /// let json = Grammar::new("text.demo")
793 /// .pattern(BeginWhile::new("^>", "^>").scope("markup.quote.demo"))
794 /// .to_json()?;
795 /// assert!(json.contains("markup.quote.demo"));
796 /// # Ok::<(), textmate_lang::Error>(())
797 /// ```
798 #[must_use]
799 pub fn scope(mut self, scope: impl Into<Text>) -> BeginWhile {
800 self.scope = Some(scope.into());
801 self
802 }
803
804 /// Scopes the region without the `begin` match (`"contentName"`).
805 ///
806 /// # Examples
807 ///
808 /// ```
809 /// use textmate_lang::{BeginWhile, Grammar};
810 ///
811 /// let json = Grammar::new("text.demo")
812 /// .pattern(BeginWhile::new("^>", "^>").content_scope("markup.quote.content.demo"))
813 /// .to_json()?;
814 /// assert!(json.contains(r#""contentName": "markup.quote.content.demo""#));
815 /// # Ok::<(), textmate_lang::Error>(())
816 /// ```
817 #[must_use]
818 pub fn content_scope(mut self, scope: impl Into<Text>) -> BeginWhile {
819 self.content_scope = Some(scope.into());
820 self
821 }
822
823 /// Sets capture `group` for both `begin` and `while` (`"captures"`).
824 ///
825 /// # Examples
826 ///
827 /// ```
828 /// use textmate_lang::{BeginWhile, Grammar};
829 ///
830 /// let json = Grammar::new("text.demo")
831 /// .pattern(BeginWhile::new("^(>)", "^(>)").capture(1, "punctuation.demo"))
832 /// .to_json()?;
833 /// assert!(json.contains(r#""captures": {"#));
834 /// # Ok::<(), textmate_lang::Error>(())
835 /// ```
836 #[must_use]
837 pub fn capture(mut self, group: u32, capture: impl Into<Capture>) -> BeginWhile {
838 self.captures.push((group, capture.into()));
839 self
840 }
841
842 /// Sets capture `group` of `begin` (`"beginCaptures"`).
843 ///
844 /// # Examples
845 ///
846 /// ```
847 /// use textmate_lang::{BeginWhile, Grammar};
848 ///
849 /// let json = Grammar::new("text.demo")
850 /// .pattern(BeginWhile::new("^(>)", "^>").begin_capture(1, "punctuation.demo"))
851 /// .to_json()?;
852 /// assert!(json.contains(r#""beginCaptures": {"#));
853 /// # Ok::<(), textmate_lang::Error>(())
854 /// ```
855 #[must_use]
856 pub fn begin_capture(mut self, group: u32, capture: impl Into<Capture>) -> BeginWhile {
857 self.begin_captures.push((group, capture.into()));
858 self
859 }
860
861 /// Sets capture `group` of `while` (`"whileCaptures"`).
862 ///
863 /// # Examples
864 ///
865 /// ```
866 /// use textmate_lang::{BeginWhile, Grammar};
867 ///
868 /// let json = Grammar::new("text.demo")
869 /// .pattern(BeginWhile::new("^>", "^(>)").while_capture(1, "punctuation.demo"))
870 /// .to_json()?;
871 /// assert!(json.contains(r#""whileCaptures": {"#));
872 /// # Ok::<(), textmate_lang::Error>(())
873 /// ```
874 #[must_use]
875 pub fn while_capture(mut self, group: u32, capture: impl Into<Capture>) -> BeginWhile {
876 self.while_captures.push((group, capture.into()));
877 self
878 }
879
880 /// Adds a rule that applies inside the region (`"patterns"`).
881 ///
882 /// # Examples
883 ///
884 /// ```
885 /// use textmate_lang::{BeginWhile, Grammar, Match};
886 ///
887 /// let json = Grammar::new("text.demo")
888 /// .pattern(BeginWhile::new("^>", "^>").pattern(Match::new(r"\*\*").scope("markup.bold.demo")))
889 /// .to_json()?;
890 /// assert!(json.contains("markup.bold.demo"));
891 /// # Ok::<(), textmate_lang::Error>(())
892 /// ```
893 #[must_use]
894 pub fn pattern(mut self, rule: impl Into<Rule>) -> BeginWhile {
895 self.patterns.push(rule.into());
896 self
897 }
898}
899
900impl From<Match> for Rule {
901 fn from(rule: Match) -> Rule {
902 Rule(Kind::Match(rule))
903 }
904}
905
906impl From<BeginEnd> for Rule {
907 fn from(rule: BeginEnd) -> Rule {
908 Rule(Kind::BeginEnd(rule))
909 }
910}
911
912impl From<BeginWhile> for Rule {
913 fn from(rule: BeginWhile) -> Rule {
914 Rule(Kind::BeginWhile(rule))
915 }
916}
917
918#[cfg(test)]
919mod tests {
920 use super::*;
921
922 #[test]
923 fn test_deep_nesting_drops_without_recursion() {
924 let mut rule = Rule::include("leaf");
925 for _ in 0..200_000 {
926 rule = BeginEnd::new("a", "b")
927 .pattern(Rule::patterns([rule]))
928 .into();
929 }
930 drop(rule);
931 let mut capture_chain = Rule::include("leaf");
932 for _ in 0..100_000 {
933 capture_chain = Match::new("(a)")
934 .capture(1, Capture::new().pattern(capture_chain))
935 .into();
936 }
937 drop(capture_chain);
938 }
939
940 #[test]
941 fn test_conversions() {
942 let a: Rule = Match::new("x").into();
943 assert!(matches!(a.0, Kind::Match(_)));
944 let b: Rule = BeginWhile::new("x", "y").into();
945 assert!(matches!(b.0, Kind::BeginWhile(_)));
946 assert_eq!(
947 Capture::from(alloc::string::String::from("s"))
948 .scope
949 .as_deref(),
950 Some("s")
951 );
952 assert_eq!(
953 Capture::from(Text::Borrowed("t")).scope.as_deref(),
954 Some("t")
955 );
956 }
957}