grit_util/
language.rs

1use std::borrow::Cow;
2
3use crate::{constants::*, ranges::EffectRange, AstNode, ByteRange, CodeRange, Range};
4use regex::Regex;
5
6pub enum GritMetaValue {
7    Underscore,
8    Dots,
9    Variable(String),
10}
11
12pub trait Language: Sized {
13    type Node<'a>: AstNode;
14
15    fn language_name(&self) -> &'static str;
16
17    fn snippet_context_strings(&self) -> &[(&'static str, &'static str)];
18
19    fn metavariable_prefix(&self) -> &'static str {
20        "$"
21    }
22
23    fn comment_prefix(&self) -> &'static str {
24        "//"
25    }
26
27    fn metavariable_prefix_substitute(&self) -> &'static str {
28        "ยต"
29    }
30
31    fn metavariable_regex(&self) -> &'static Regex {
32        &VARIABLE_REGEX
33    }
34
35    fn replaced_metavariable_regex(&self) -> &'static Regex {
36        &REPLACED_VARIABLE_REGEX
37    }
38
39    fn metavariable_bracket_regex(&self) -> &'static Regex {
40        &BRACKET_VAR_REGEX
41    }
42
43    fn exact_variable_regex(&self) -> &'static Regex {
44        &EXACT_VARIABLE_REGEX
45    }
46
47    fn exact_replaced_variable_regex(&self) -> &'static Regex {
48        &EXACT_REPLACED_VARIABLE_REGEX
49    }
50
51    fn is_comment(&self, node: &Self::Node<'_>) -> bool;
52
53    fn is_metavariable(&self, node: &Self::Node<'_>) -> bool;
54
55    #[allow(unused_variables)]
56    fn is_statement(&self, node: &Self::Node<'_>) -> bool {
57        false
58    }
59
60    // assumes trim doesn't do anything otherwise range is off
61    fn comment_text_range(&self, node: &Self::Node<'_>) -> Option<ByteRange> {
62        Some(node.byte_range())
63    }
64
65    /// Removes the padding from every line in the snippet identified by the
66    /// given `range`, such that the first line of the snippet is left-aligned.
67    fn align_padding<'a>(
68        &self,
69        node: &Self::Node<'a>,
70        range: &CodeRange,
71        skip_ranges: &[CodeRange],
72        new_padding: Option<usize>,
73        offset: usize,
74        substitutions: &mut [(EffectRange, String)],
75    ) -> Cow<'a, str>;
76
77    /// Pads `snippet` by applying the given `padding` to every line.
78    ///
79    /// Takes padding rules for whitespace-significant languages into account.
80    fn pad_snippet<'a>(&self, snippet: &'a str, padding: &str) -> Cow<'a, str>;
81
82    fn substitute_metavariable_prefix(&self, src: &str) -> String {
83        self.metavariable_regex()
84            .replace_all(
85                src,
86                format!("{}$1", self.metavariable_prefix_substitute()).as_str(),
87            )
88            .to_string()
89    }
90
91    fn snippet_metavariable_to_grit_metavariable(&self, src: &str) -> Option<GritMetaValue> {
92        src.trim()
93            .strip_prefix(self.metavariable_prefix_substitute())
94            .map(|s| match s {
95                "_" => GritMetaValue::Underscore,
96                "..." => GritMetaValue::Dots,
97                _ => {
98                    let mut s = s.to_owned();
99                    s.insert_str(0, self.metavariable_prefix());
100                    GritMetaValue::Variable(s)
101                }
102            })
103    }
104
105    /// Check for nodes that should be removed or replaced.
106    ///
107    /// This is used to "repair" the program after rewriting, such as by
108    /// deleting orphaned ranges (like a variable declaration without any
109    /// variables). If the node should be removed, it adds a range with a `None`
110    /// value. If the node should be replaced, it adds a range with the
111    /// replacement value.
112    #[allow(unused_variables)]
113    fn check_replacements(&self, node: Self::Node<'_>, replacements: &mut Vec<Replacement>) {}
114
115    #[allow(unused_variables)]
116    fn take_padding(&self, current: char, next: Option<char>) -> Option<char> {
117        if current.is_whitespace() {
118            Some(current)
119        } else {
120            None
121        }
122    }
123
124    fn get_skip_padding_ranges(&self, node: &Self::Node<'_>) -> Vec<CodeRange>;
125
126    /// Whether snippets should be padded.
127    ///
128    /// This is generally `true` for languages with relevant whitespace.
129    fn should_pad_snippet(&self) -> bool {
130        false
131    }
132
133    fn make_single_line_comment(&self, text: &str) -> String {
134        format!("// {text}\n")
135    }
136}
137
138#[derive(Clone, Debug)]
139pub struct Replacement {
140    pub range: Range,
141    pub replacement: &'static str,
142}
143
144impl Replacement {
145    pub fn new(range: Range, replacement: &'static str) -> Self {
146        Self { range, replacement }
147    }
148}
149
150impl From<&Replacement> for (std::ops::Range<usize>, usize) {
151    fn from(replacement: &Replacement) -> Self {
152        (
153            (replacement.range.start_byte as usize)..(replacement.range.end_byte as usize),
154            replacement.replacement.len(),
155        )
156    }
157}