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#[derive(Clone, Debug, PartialEq, Eq)]
11pub enum RewriteNode<'db> {
12 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 pub fn from_ast(node: &impl TypedSyntaxNode<'db>) -> Self {
55 RewriteNode::Copied(node.as_syntax_node())
56 }
57
58 pub fn from_ast_trimmed(node: &impl TypedSyntaxNode<'db>) -> Self {
60 Self::new_trimmed(node.as_syntax_node())
61 }
62
63 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 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 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 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 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 return self;
143 }
144 &mut self.modify(db).children.as_mut().unwrap()[index]
145 }
146
147 pub fn set_str(&mut self, s: String) {
149 *self = RewriteNode::Text(s)
150 }
151 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 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 if name.is_empty() {
181 pending_text.push('$');
182 continue;
183 }
184 if !pending_text.is_empty() {
187 children.push(RewriteNode::text(&pending_text));
188 pending_text.clear();
189 }
190 children.push(
193 patches.get(&name).cloned().unwrap_or_else(|| panic!("No patch named {name}.")),
194 );
195 }
196 if !pending_text.is_empty() {
198 children.push(RewriteNode::text(&pending_text));
199 }
200
201 RewriteNode::new_modified(children)
202 }
203
204 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 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#[derive(Clone, Debug, PartialEq, Eq)]
233pub struct ModifiedNode<'db> {
234 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 pub fn new(db: &'db dyn Database, origin: &impl TypedSyntaxNode<'db>) -> Self {
250 Self::new_ex(db, &origin.as_syntax_node())
251 }
252
253 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 pub fn build(mut self) -> (String, Vec<CodeMapping>) {
265 self.code_mappings
267 .push(CodeMapping { span: TextSpan::from_str(&self.code), origin: self.origin });
268 (self.code, self.code_mappings)
269 }
270
271 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}