cairo_lang_defs/
patcher.rs

1use cairo_lang_filesystem::ids::{CodeMapping, CodeOrigin};
2use cairo_lang_filesystem::span::{TextOffset, TextSpan, TextWidth};
3use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode};
4use cairo_lang_utils::extract_matches;
5use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
6use itertools::Itertools;
7use salsa::Database;
8
9/// Interface for modifying syntax nodes.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub enum RewriteNode<'db> {
12    /// A rewrite node that represents a trimmed copy of a syntax node:
13    /// one with the leading and trailing trivia excluded.
14    Trimmed {
15        node: SyntaxNode<'db>,
16        trim_left: bool,
17        trim_right: bool,
18    },
19    Copied(SyntaxNode<'db>),
20    Modified(ModifiedNode<'db>),
21    Mapped {
22        origin: TextSpan,
23        node: Box<RewriteNode<'db>>,
24    },
25    Text(String),
26    TextAndMapping(String, Vec<CodeMapping>),
27}
28impl<'db> RewriteNode<'db> {
29    pub fn new_trimmed(syntax_node: SyntaxNode<'db>) -> Self {
30        Self::Trimmed { node: syntax_node, trim_left: true, trim_right: true }
31    }
32
33    pub fn new_modified(children: Vec<RewriteNode<'db>>) -> Self {
34        Self::Modified(ModifiedNode { children: Some(children) })
35    }
36
37    pub fn text(text: &str) -> Self {
38        Self::Text(text.to_string())
39    }
40
41    pub fn mapped_text(
42        text: impl Into<String>,
43        db: &dyn Database,
44        origin: &impl TypedSyntaxNode<'db>,
45    ) -> Self {
46        RewriteNode::Text(text.into()).mapped(db, origin)
47    }
48
49    pub fn empty() -> Self {
50        Self::text("")
51    }
52
53    /// Creates a rewrite node from an AST object.
54    pub fn from_ast(node: &impl TypedSyntaxNode<'db>) -> Self {
55        RewriteNode::Copied(node.as_syntax_node())
56    }
57
58    /// Creates a rewrite node from an AST object with trimming.
59    pub fn from_ast_trimmed(node: &impl TypedSyntaxNode<'db>) -> Self {
60        Self::new_trimmed(node.as_syntax_node())
61    }
62
63    /// Prepares a node for modification.
64    pub fn modify(&mut self, db: &'db dyn Database) -> &mut ModifiedNode<'db> {
65        match self {
66            RewriteNode::Copied(syntax_node) => {
67                *self = RewriteNode::new_modified(
68                    syntax_node.get_children(db).iter().copied().map(RewriteNode::Copied).collect(),
69                );
70                extract_matches!(self, RewriteNode::Modified)
71            }
72            RewriteNode::Trimmed { node, trim_left, trim_right } => {
73                let children = node.get_children(db);
74                let num_children = children.len();
75                let mut new_children = Vec::new();
76
77                // Get the index of the leftmost nonempty child.
78                let Some(left_idx) =
79                    children.iter().position(|child| child.width(db) != TextWidth::default())
80                else {
81                    *self = RewriteNode::Modified(ModifiedNode { children: None });
82                    return extract_matches!(self, RewriteNode::Modified);
83                };
84                // Get the index of the rightmost nonempty child.
85                let right_idx = children
86                    .iter()
87                    .rposition(|child| child.width(db) != TextWidth::default())
88                    .unwrap();
89                new_children.extend(itertools::repeat_n(
90                    RewriteNode::Modified(ModifiedNode { children: None }),
91                    left_idx,
92                ));
93
94                // The number of children between the first and last nonempty nodes.
95                let num_middle = right_idx - left_idx + 1;
96                let mut children_iter = children.iter().skip(left_idx);
97                match num_middle {
98                    1 => {
99                        new_children.push(RewriteNode::Trimmed {
100                            node: *children_iter.next().unwrap(),
101                            trim_left: *trim_left,
102                            trim_right: *trim_right,
103                        });
104                    }
105                    _ => {
106                        new_children.push(RewriteNode::Trimmed {
107                            node: *children_iter.next().unwrap(),
108                            trim_left: *trim_left,
109                            trim_right: false,
110                        });
111                        for _ in 0..(num_middle - 2) {
112                            let child = *children_iter.next().unwrap();
113                            new_children.push(RewriteNode::Copied(child));
114                        }
115                        new_children.push(RewriteNode::Trimmed {
116                            node: *children_iter.next().unwrap(),
117                            trim_left: false,
118                            trim_right: *trim_right,
119                        });
120                    }
121                };
122                new_children.extend(itertools::repeat_n(
123                    RewriteNode::Modified(ModifiedNode { children: None }),
124                    num_children - right_idx - 1,
125                ));
126
127                *self = RewriteNode::Modified(ModifiedNode { children: Some(new_children) });
128                extract_matches!(self, RewriteNode::Modified)
129            }
130            RewriteNode::Modified(modified) => modified,
131            RewriteNode::Text(_) | RewriteNode::TextAndMapping(_, _) => {
132                panic!("A text node can't be modified")
133            }
134            RewriteNode::Mapped { .. } => panic!("A mapped node can't be modified"),
135        }
136    }
137
138    /// Prepares a node for modification and returns a specific child.
139    pub fn modify_child(&mut self, db: &'db dyn Database, index: usize) -> &mut RewriteNode<'db> {
140        if matches!(self, RewriteNode::Modified(ModifiedNode { children: None })) {
141            // Modification of an empty node is idempotent.
142            return self;
143        }
144        &mut self.modify(db).children.as_mut().unwrap()[index]
145    }
146
147    /// Replaces this node with text.
148    pub fn set_str(&mut self, s: String) {
149        *self = RewriteNode::Text(s)
150    }
151    /// Creates a new Rewrite node by interpolating a string with patches.
152    /// Each substring of the form `$<name>$` is replaced with syntax nodes from `patches`.
153    /// A `$$` substring is replaced with `$`.
154    pub fn interpolate_patched(
155        code: &str,
156        patches: &UnorderedHashMap<String, RewriteNode<'db>>,
157    ) -> RewriteNode<'db> {
158        let mut chars = code.chars().peekable();
159        let mut pending_text = String::new();
160        let mut children = Vec::new();
161        while let Some(c) = chars.next() {
162            if c != '$' {
163                pending_text.push(c);
164                continue;
165            }
166
167            // An opening $ was detected.
168
169            // Read the name
170            let mut name = String::new();
171            for c in chars.by_ref() {
172                if c == '$' {
173                    break;
174                }
175                name.push(c);
176            }
177
178            // A closing $ was found.
179            // If the string between the `$`s is empty - push a single `$` to the output.
180            if name.is_empty() {
181                pending_text.push('$');
182                continue;
183            }
184            // If the string wasn't empty and there is some pending text, first flush it as a text
185            // child.
186            if !pending_text.is_empty() {
187                children.push(RewriteNode::text(&pending_text));
188                pending_text.clear();
189            }
190            // Replace the substring with the relevant rewrite node.
191            // TODO(yuval): this currently panics. Fix it.
192            children.push(
193                patches.get(&name).cloned().unwrap_or_else(|| panic!("No patch named {name}.")),
194            );
195        }
196        // Flush the remaining text as a text child.
197        if !pending_text.is_empty() {
198            children.push(RewriteNode::text(&pending_text));
199        }
200
201        RewriteNode::new_modified(children)
202    }
203
204    /// Creates a new Rewrite node by inserting a `separator` between each two given children.
205    pub fn interspersed(
206        children: impl IntoIterator<Item = RewriteNode<'db>>,
207        separator: RewriteNode<'db>,
208    ) -> RewriteNode<'db> {
209        RewriteNode::new_modified(itertools::intersperse(children, separator).collect_vec())
210    }
211
212    /// Creates a new rewrite node wrapped in a mapping to the original code.
213    pub fn mapped(self, db: &dyn Database, origin: &impl TypedSyntaxNode<'db>) -> Self {
214        RewriteNode::Mapped {
215            origin: origin.as_syntax_node().span_without_trivia(db),
216            node: Box::new(self),
217        }
218    }
219}
220impl<'db> Default for RewriteNode<'db> {
221    fn default() -> Self {
222        Self::empty()
223    }
224}
225impl<'db> From<SyntaxNode<'db>> for RewriteNode<'db> {
226    fn from(node: SyntaxNode<'db>) -> Self {
227        RewriteNode::Copied(node)
228    }
229}
230
231/// A modified rewrite node.
232#[derive(Clone, Debug, PartialEq, Eq)]
233pub struct ModifiedNode<'db> {
234    /// Children of the node.
235    /// Can be None, in which case this is an empty node (of width 0). It's not the same as
236    /// Some(vec![]) - A child can be (idempotently) modified for None, whereas modifying a child
237    /// for Some(vec![]) would panic.
238    pub children: Option<Vec<RewriteNode<'db>>>,
239}
240
241pub struct PatchBuilder<'a> {
242    pub db: &'a dyn Database,
243    code: String,
244    code_mappings: Vec<CodeMapping>,
245    origin: CodeOrigin,
246}
247impl<'db> PatchBuilder<'db> {
248    /// Creates a new patch builder, originating from `origin` typed node.
249    pub fn new(db: &'db dyn Database, origin: &impl TypedSyntaxNode<'db>) -> Self {
250        Self::new_ex(db, &origin.as_syntax_node())
251    }
252
253    /// Creates a new patch builder, originating from `origin` node.
254    pub fn new_ex(db: &'db dyn Database, origin: &SyntaxNode<'db>) -> Self {
255        Self {
256            db,
257            code: String::default(),
258            code_mappings: vec![],
259            origin: CodeOrigin::Span(origin.span_without_trivia(db)),
260        }
261    }
262
263    /// Builds the resulting code and code mappings.
264    pub fn build(mut self) -> (String, Vec<CodeMapping>) {
265        // Adds the mapping to the original node from all code not previously mapped.
266        self.code_mappings
267            .push(CodeMapping { span: TextSpan::from_str(&self.code), origin: self.origin });
268        (self.code, self.code_mappings)
269    }
270
271    /// Builds the patcher into a rewrite node enabling adding it to other patchers.
272    pub fn into_rewrite_node(self) -> RewriteNode<'db> {
273        let (code, mappings) = self.build();
274        RewriteNode::TextAndMapping(code, mappings)
275    }
276
277    pub fn add_char(&mut self, c: char) {
278        self.code.push(c);
279    }
280
281    pub fn add_str(&mut self, s: &str) {
282        self.code += s;
283    }
284
285    pub fn add_modified(&mut self, node: RewriteNode<'db>) {
286        match node {
287            RewriteNode::Copied(node) => self.add_node(node),
288            RewriteNode::Mapped { origin, node } => self.add_mapped(*node, origin),
289            RewriteNode::Trimmed { node, trim_left, trim_right } => {
290                self.add_trimmed_node(node, trim_left, trim_right)
291            }
292            RewriteNode::Modified(modified) => {
293                if let Some(children) = modified.children {
294                    for child in children {
295                        self.add_modified(child)
296                    }
297                }
298            }
299            RewriteNode::Text(s) => self.add_str(s.as_str()),
300            RewriteNode::TextAndMapping(s, mappings) => {
301                let mapping_fix = TextWidth::from_str(&self.code);
302                self.add_str(&s);
303                self.code_mappings.extend(mappings.into_iter().map(|mut mapping| {
304                    mapping.span.start = mapping.span.start.add_width(mapping_fix);
305                    mapping.span.end = mapping.span.end.add_width(mapping_fix);
306                    mapping
307                }));
308            }
309        }
310    }
311
312    pub fn add_node(&mut self, node: SyntaxNode<'db>) {
313        let start = TextOffset::from_str(&self.code);
314        let orig_span = node.span(self.db);
315        self.code_mappings.push(CodeMapping {
316            span: TextSpan::new_with_width(start, orig_span.width()),
317            origin: CodeOrigin::Start(orig_span.start),
318        });
319        self.code += node.get_text(self.db);
320    }
321
322    fn add_mapped(&mut self, node: RewriteNode<'db>, origin: TextSpan) {
323        let start = TextOffset::from_str(&self.code);
324        self.add_modified(node);
325        let end = TextOffset::from_str(&self.code);
326        self.code_mappings.push(CodeMapping {
327            span: TextSpan::new(start, end),
328            origin: CodeOrigin::Span(origin),
329        });
330    }
331
332    fn add_trimmed_node(&mut self, node: SyntaxNode<'db>, trim_left: bool, trim_right: bool) {
333        let trimmed = node.span_without_trivia(self.db);
334        let orig_start = if trim_left { trimmed.start } else { node.span(self.db).start };
335        let orig_end = if trim_right { trimmed.end } else { node.span(self.db).end };
336        let origin_span = TextSpan::new(orig_start, orig_end);
337
338        let text = node.get_text_of_span(self.db, origin_span);
339        let start = TextOffset::from_str(&self.code);
340
341        self.code += text;
342
343        self.code_mappings.push(CodeMapping {
344            span: TextSpan::new_with_width(start, TextWidth::from_str(text)),
345            origin: CodeOrigin::Start(orig_start),
346        });
347    }
348}