1use airl_ir::module::{FuncDef, Module};
4use airl_ir::version::VersionId;
5
6use crate::impact::{self, Impact};
7use crate::ops::{Patch, PatchOp};
8use crate::traverse;
9use crate::validate;
10use crate::PatchError;
11
12#[derive(Clone, Debug)]
14pub struct PatchResult {
15 pub new_module: Module,
17 pub new_version: String,
19 pub impact: Impact,
21}
22
23pub fn apply_patch(module: &Module, patch: &Patch) -> Result<PatchResult, PatchError> {
28 validate::validate_patch(module, patch)?;
30
31 let impact_result = impact::analyze_impact(module, &patch.operations);
33
34 let mut new_module = module.clone();
36
37 for op in &patch.operations {
38 apply_op(&mut new_module, op)?;
39 }
40
41 let version = VersionId::compute(&new_module);
43 let version_hex = version.to_hex();
44
45 Ok(PatchResult {
46 new_module,
47 new_version: version_hex,
48 impact: impact_result,
49 })
50}
51
52fn apply_op(module: &mut Module, op: &PatchOp) -> Result<(), PatchError> {
54 match op {
55 PatchOp::ReplaceNode {
56 target,
57 replacement,
58 } => {
59 let mut found = false;
61 let new_functions: Vec<FuncDef> = module
62 .module
63 .functions
64 .iter()
65 .map(|func| {
66 if let Some(new_body) =
67 traverse::replace_node_in_tree(&func.body, target, replacement)
68 {
69 found = true;
70 FuncDef {
71 body: new_body,
72 ..func.clone()
73 }
74 } else {
75 func.clone()
76 }
77 })
78 .collect();
79
80 if !found {
81 return Err(PatchError::NodeNotFound {
82 node_id: target.to_string(),
83 });
84 }
85 module.module.functions = new_functions;
86 Ok(())
87 }
88
89 PatchOp::AddFunction { func } => {
90 module.module.functions.push(func.clone());
91 Ok(())
92 }
93
94 PatchOp::RemoveFunction { func_id } => {
95 module.module.functions.retain(|f| &f.id != func_id);
96 Ok(())
97 }
98
99 PatchOp::AddImport { import } => {
100 module.module.imports.push(import.clone());
101 Ok(())
102 }
103
104 PatchOp::RemoveImport { import } => {
105 module
106 .module
107 .imports
108 .retain(|i| i.module != import.module || i.items != import.items);
109 Ok(())
110 }
111
112 PatchOp::RenameSymbol {
113 old_name,
114 new_name,
115 scope,
116 } => {
117 let new_functions: Vec<FuncDef> = module
118 .module
119 .functions
120 .iter()
121 .map(|func| {
122 let in_scope = scope.as_ref().is_none_or(|s| &func.id == s);
123 if in_scope {
124 let new_body = traverse::rename_in_tree(&func.body, old_name, new_name);
125 let new_name_field = if func.name == *old_name {
126 new_name.clone()
127 } else {
128 func.name.clone()
129 };
130 FuncDef {
131 name: new_name_field,
132 body: new_body,
133 ..func.clone()
134 }
135 } else {
136 func.clone()
137 }
138 })
139 .collect();
140 module.module.functions = new_functions;
141 Ok(())
142 }
143
144 PatchOp::AddEffect { func_id, effect } => {
145 for func in &mut module.module.functions {
146 if &func.id == func_id {
147 if !func.effects.contains(effect) {
148 func.effects.push(effect.clone());
149 }
150 return Ok(());
151 }
152 }
153 Err(PatchError::FunctionNotFound {
154 func_id: func_id.to_string(),
155 })
156 }
157
158 PatchOp::RemoveEffect { func_id, effect } => {
159 for func in &mut module.module.functions {
160 if &func.id == func_id {
161 func.effects.retain(|e| e != effect);
162 return Ok(());
163 }
164 }
165 Err(PatchError::FunctionNotFound {
166 func_id: func_id.to_string(),
167 })
168 }
169 }
170}