1use cairo_lang_filesystem::ids::{CodeMapping, CodeOrigin};
2use cairo_lang_filesystem::span::{TextOffset, TextSpan, TextWidth};
3use cairo_lang_syntax::node::db::SyntaxGroup;
4use cairo_lang_syntax::node::{SyntaxNode, TypedSyntaxNode};
5use cairo_lang_utils::extract_matches;
6use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
7use itertools::Itertools;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11pub enum RewriteNode {
12 Trimmed {
15 node: SyntaxNode,
16 trim_left: bool,
17 trim_right: bool,
18 },
19 Copied(SyntaxNode),
20 Modified(ModifiedNode),
21 Mapped {
22 origin: TextSpan,
23 node: Box<RewriteNode>,
24 },
25 Text(String),
26 TextAndMapping(String, Vec<CodeMapping>),
27}
28impl RewriteNode {
29 pub fn new_trimmed(syntax_node: SyntaxNode) -> Self {
30 Self::Trimmed { node: syntax_node, trim_left: true, trim_right: true }
31 }
32
33 pub fn new_modified(children: Vec<RewriteNode>) -> 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 SyntaxGroup,
44 origin: &impl TypedSyntaxNode,
45 ) -> Self {
46 RewriteNode::Text(text.into()).mapped(db, origin)
47 }
48
49 pub fn empty() -> Self {
50 Self::text("")
51 }
52
53 pub fn from_ast(node: &impl TypedSyntaxNode) -> Self {
55 RewriteNode::Copied(node.as_syntax_node())
56 }
57
58 pub fn from_ast_trimmed(node: &impl TypedSyntaxNode) -> Self {
60 Self::new_trimmed(node.as_syntax_node())
61 }
62
63 pub fn modify(&mut self, db: &dyn SyntaxGroup) -> &mut ModifiedNode {
65 match self {
66 RewriteNode::Copied(syntax_node) => {
67 *self = RewriteNode::new_modified(
68 db.get_children(syntax_node.clone())
69 .iter()
70 .cloned()
71 .map(RewriteNode::Copied)
72 .collect(),
73 );
74 extract_matches!(self, RewriteNode::Modified)
75 }
76 RewriteNode::Trimmed { node, trim_left, trim_right } => {
77 let children = db.get_children(node.clone());
78 let num_children = children.len();
79 let mut new_children = Vec::new();
80
81 let Some(left_idx) =
83 children.iter().position(|child| child.width(db) != TextWidth::default())
84 else {
85 *self = RewriteNode::Modified(ModifiedNode { children: None });
86 return extract_matches!(self, RewriteNode::Modified);
87 };
88 let right_idx = children
90 .iter()
91 .rposition(|child| child.width(db) != TextWidth::default())
92 .unwrap();
93 new_children.extend(itertools::repeat_n(
94 RewriteNode::Modified(ModifiedNode { children: None }),
95 left_idx,
96 ));
97
98 let num_middle = right_idx - left_idx + 1;
100 let children = db.get_children(node.clone());
101 let mut children_iter = children.iter().skip(left_idx);
102 match num_middle {
103 1 => {
104 new_children.push(RewriteNode::Trimmed {
105 node: children_iter.next().unwrap().clone(),
106 trim_left: *trim_left,
107 trim_right: *trim_right,
108 });
109 }
110 _ => {
111 new_children.push(RewriteNode::Trimmed {
112 node: children_iter.next().unwrap().clone(),
113 trim_left: *trim_left,
114 trim_right: false,
115 });
116 for _ in 0..(num_middle - 2) {
117 let child = children_iter.next().unwrap().clone();
118 new_children.push(RewriteNode::Copied(child));
119 }
120 new_children.push(RewriteNode::Trimmed {
121 node: children_iter.next().unwrap().clone(),
122 trim_left: false,
123 trim_right: *trim_right,
124 });
125 }
126 };
127 new_children.extend(itertools::repeat_n(
128 RewriteNode::Modified(ModifiedNode { children: None }),
129 num_children - right_idx - 1,
130 ));
131
132 *self = RewriteNode::Modified(ModifiedNode { children: Some(new_children) });
133 extract_matches!(self, RewriteNode::Modified)
134 }
135 RewriteNode::Modified(modified) => modified,
136 RewriteNode::Text(_) | RewriteNode::TextAndMapping(_, _) => {
137 panic!("A text node can't be modified")
138 }
139 RewriteNode::Mapped { .. } => panic!("A mapped node can't be modified"),
140 }
141 }
142
143 pub fn modify_child(&mut self, db: &dyn SyntaxGroup, index: usize) -> &mut RewriteNode {
145 if matches!(self, RewriteNode::Modified(ModifiedNode { children: None })) {
146 return self;
148 }
149 &mut self.modify(db).children.as_mut().unwrap()[index]
150 }
151
152 pub fn set_str(&mut self, s: String) {
154 *self = RewriteNode::Text(s)
155 }
156 pub fn interpolate_patched(
160 code: &str,
161 patches: &UnorderedHashMap<String, RewriteNode>,
162 ) -> RewriteNode {
163 let mut chars = code.chars().peekable();
164 let mut pending_text = String::new();
165 let mut children = Vec::new();
166 while let Some(c) = chars.next() {
167 if c != '$' {
168 pending_text.push(c);
169 continue;
170 }
171
172 let mut name = String::new();
176 for c in chars.by_ref() {
177 if c == '$' {
178 break;
179 }
180 name.push(c);
181 }
182
183 if name.is_empty() {
186 pending_text.push('$');
187 continue;
188 }
189 if !pending_text.is_empty() {
192 children.push(RewriteNode::text(&pending_text));
193 pending_text.clear();
194 }
195 children.push(
198 patches.get(&name).cloned().unwrap_or_else(|| panic!("No patch named {}.", name)),
199 );
200 }
201 if !pending_text.is_empty() {
203 children.push(RewriteNode::text(&pending_text));
204 }
205
206 RewriteNode::new_modified(children)
207 }
208
209 pub fn interspersed(
211 children: impl IntoIterator<Item = RewriteNode>,
212 separator: RewriteNode,
213 ) -> RewriteNode {
214 RewriteNode::new_modified(itertools::intersperse(children, separator).collect_vec())
215 }
216
217 pub fn mapped(self, db: &dyn SyntaxGroup, origin: &impl TypedSyntaxNode) -> Self {
219 RewriteNode::Mapped {
220 origin: origin.as_syntax_node().span_without_trivia(db),
221 node: Box::new(self),
222 }
223 }
224}
225impl Default for RewriteNode {
226 fn default() -> Self {
227 Self::empty()
228 }
229}
230impl From<SyntaxNode> for RewriteNode {
231 fn from(node: SyntaxNode) -> Self {
232 RewriteNode::Copied(node)
233 }
234}
235
236#[derive(Clone, Debug, PartialEq, Eq)]
238pub struct ModifiedNode {
239 pub children: Option<Vec<RewriteNode>>,
244}
245
246pub struct PatchBuilder<'a> {
247 pub db: &'a dyn SyntaxGroup,
248 code: String,
249 code_mappings: Vec<CodeMapping>,
250 origin: CodeOrigin,
251}
252impl<'a> PatchBuilder<'a> {
253 pub fn new(db: &'a dyn SyntaxGroup, origin: &impl TypedSyntaxNode) -> Self {
255 Self::new_ex(db, &origin.as_syntax_node())
256 }
257
258 pub fn new_ex(db: &'a dyn SyntaxGroup, origin: &SyntaxNode) -> Self {
260 Self {
261 db,
262 code: String::default(),
263 code_mappings: vec![],
264 origin: CodeOrigin::Span(origin.span_without_trivia(db)),
265 }
266 }
267
268 pub fn build(mut self) -> (String, Vec<CodeMapping>) {
270 self.code_mappings
272 .push(CodeMapping { span: TextSpan::from_str(&self.code), origin: self.origin });
273 (self.code, self.code_mappings)
274 }
275
276 pub fn into_rewrite_node(self) -> RewriteNode {
278 let (code, mappings) = self.build();
279 RewriteNode::TextAndMapping(code, mappings)
280 }
281
282 pub fn add_char(&mut self, c: char) {
283 self.code.push(c);
284 }
285
286 pub fn add_str(&mut self, s: &str) {
287 self.code += s;
288 }
289
290 pub fn add_modified(&mut self, node: RewriteNode) {
291 match node {
292 RewriteNode::Copied(node) => self.add_node(node),
293 RewriteNode::Mapped { origin, node } => self.add_mapped(*node, origin),
294 RewriteNode::Trimmed { node, trim_left, trim_right } => {
295 self.add_trimmed_node(node, trim_left, trim_right)
296 }
297 RewriteNode::Modified(modified) => {
298 if let Some(children) = modified.children {
299 for child in children {
300 self.add_modified(child)
301 }
302 }
303 }
304 RewriteNode::Text(s) => self.add_str(s.as_str()),
305 RewriteNode::TextAndMapping(s, mappings) => {
306 let mapping_fix = TextWidth::from_str(&self.code);
307 self.add_str(&s);
308 self.code_mappings.extend(mappings.into_iter().map(|mut mapping| {
309 mapping.span.start = mapping.span.start.add_width(mapping_fix);
310 mapping.span.end = mapping.span.end.add_width(mapping_fix);
311 mapping
312 }));
313 }
314 }
315 }
316
317 pub fn add_node(&mut self, node: SyntaxNode) {
318 let start = TextOffset::from_str(&self.code);
319 let orig_span = node.span(self.db);
320 self.code_mappings.push(CodeMapping {
321 span: TextSpan { start, end: start.add_width(orig_span.width()) },
322 origin: CodeOrigin::Start(orig_span.start),
323 });
324 self.code += &node.get_text(self.db);
325 }
326
327 fn add_mapped(&mut self, node: RewriteNode, origin: TextSpan) {
328 let start = TextOffset::from_str(&self.code);
329 self.add_modified(node);
330 let end = TextOffset::from_str(&self.code);
331 self.code_mappings
332 .push(CodeMapping { span: TextSpan { start, end }, origin: CodeOrigin::Span(origin) });
333 }
334
335 fn add_trimmed_node(&mut self, node: SyntaxNode, trim_left: bool, trim_right: bool) {
336 let TextSpan { start: trimmed_start, end: trimmed_end } = node.span_without_trivia(self.db);
337 let orig_start = if trim_left { trimmed_start } else { node.span(self.db).start };
338 let orig_end = if trim_right { trimmed_end } else { node.span(self.db).end };
339 let origin_span = TextSpan { start: orig_start, end: orig_end };
340
341 let text = node.get_text_of_span(self.db, origin_span);
342 let start = TextOffset::from_str(&self.code);
343
344 self.code += &text;
345
346 self.code_mappings.push(CodeMapping {
347 span: TextSpan { start, end: start.add_width(TextWidth::from_str(&text)) },
348 origin: CodeOrigin::Start(orig_start),
349 });
350 }
351}