Skip to main content

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}