1#![warn(missing_docs)]
2
3use nargo_types::NargoValue;
8
9use crate::expr::JsExpr;
10use crate::program::{IRModule, JsProgram};
11use crate::stmt::JsStmt;
12use crate::types::Trivia;
13use nargo_types::Span;
14
15pub struct ExprOptimizer;
19
20impl ExprOptimizer {
21 pub fn optimize(expr: &mut JsExpr) {
26 match expr {
27 JsExpr::Unary { op, argument, .. } => {
28 Self::optimize(argument);
29 if let JsExpr::Literal(value, span, trivia) = &**argument {
31 match op.as_str() {
32 "!" => {
33 if let Some(b) = value.as_bool() {
34 *expr = JsExpr::Literal(NargoValue::Bool(!b), *span, trivia.clone());
35 }
36 }
37 "-" => {
38 if let Some(n) = value.as_number() {
39 *expr = JsExpr::Literal(NargoValue::Number(-n), *span, trivia.clone());
40 }
41 }
42 "+" => {
43 if let Some(n) = value.as_number() {
44 *expr = JsExpr::Literal(NargoValue::Number(n), *span, trivia.clone());
45 }
46 else if let Some(s) = value.as_str() {
47 if let Ok(n) = s.parse::<f64>() {
48 *expr = JsExpr::Literal(NargoValue::Number(n), *span, trivia.clone());
49 }
50 }
51 }
52 "~" => {
53 if let Some(n) = value.as_number() {
54 let int_val = n as i64;
55 *expr = JsExpr::Literal(NargoValue::Number((!int_val) as f64), *span, trivia.clone());
56 }
57 }
58 _ => return,
59 };
60 }
61 }
62 JsExpr::Binary { left, op, right, span, trivia } => {
63 Self::optimize(left);
64 Self::optimize(right);
65 if let (JsExpr::Literal(left_val, _, _), JsExpr::Literal(right_val, _, _)) = (&**left, &**right) {
67 match op.as_str() {
68 "+" => {
69 if let (Some(l), Some(r)) = (left_val.as_number(), right_val.as_number()) {
70 *expr = JsExpr::Literal(NargoValue::Number(l + r), *span, trivia.clone());
71 }
72 else if let (Some(l), Some(r)) = (left_val.as_str(), right_val.as_str()) {
73 *expr = JsExpr::Literal(NargoValue::String(format!("{}{}", l, r)), *span, trivia.clone());
74 }
75 else if let (Some(l), Some(r)) = (left_val.as_str(), right_val.as_number()) {
76 *expr = JsExpr::Literal(NargoValue::String(format!("{}{}", l, r)), *span, trivia.clone());
77 }
78 else if let (Some(l), Some(r)) = (left_val.as_number(), right_val.as_str()) {
79 *expr = JsExpr::Literal(NargoValue::String(format!("{}{}", l, r)), *span, trivia.clone());
80 }
81 }
82 "-" => {
83 if let (Some(l), Some(r)) = (left_val.as_number(), right_val.as_number()) {
84 *expr = JsExpr::Literal(NargoValue::Number(l - r), *span, trivia.clone());
85 }
86 }
87 "*" => {
88 if let (Some(l), Some(r)) = (left_val.as_number(), right_val.as_number()) {
89 *expr = JsExpr::Literal(NargoValue::Number(l * r), *span, trivia.clone());
90 }
91 }
92 "/" => {
93 if let (Some(l), Some(r)) = (left_val.as_number(), right_val.as_number()) {
94 *expr = JsExpr::Literal(NargoValue::Number(l / r), *span, trivia.clone());
95 }
96 }
97 "%" => {
98 if let (Some(l), Some(r)) = (left_val.as_number(), right_val.as_number()) {
99 *expr = JsExpr::Literal(NargoValue::Number(l % r), *span, trivia.clone());
100 }
101 }
102 "==" | "!=" | "===" | "!==" => {
103 let result = left_val == right_val;
104 let final_result = if op == "!=" || op == "!==" { !result } else { result };
105 *expr = JsExpr::Literal(NargoValue::Bool(final_result), *span, trivia.clone());
106 }
107 "<" => {
108 if let (Some(l), Some(r)) = (left_val.as_number(), right_val.as_number()) {
109 *expr = JsExpr::Literal(NargoValue::Bool(l < r), *span, trivia.clone());
110 }
111 }
112 "<=" => {
113 if let (Some(l), Some(r)) = (left_val.as_number(), right_val.as_number()) {
114 *expr = JsExpr::Literal(NargoValue::Bool(l <= r), *span, trivia.clone());
115 }
116 }
117 ">" => {
118 if let (Some(l), Some(r)) = (left_val.as_number(), right_val.as_number()) {
119 *expr = JsExpr::Literal(NargoValue::Bool(l > r), *span, trivia.clone());
120 }
121 }
122 ">=" => {
123 if let (Some(l), Some(r)) = (left_val.as_number(), right_val.as_number()) {
124 *expr = JsExpr::Literal(NargoValue::Bool(l >= r), *span, trivia.clone());
125 }
126 }
127 "&&" => {
128 if let (Some(l), Some(r)) = (left_val.as_bool(), right_val.as_bool()) {
129 *expr = JsExpr::Literal(NargoValue::Bool(l && r), *span, trivia.clone());
130 }
131 }
132 "||" => {
133 if let (Some(l), Some(r)) = (left_val.as_bool(), right_val.as_bool()) {
134 *expr = JsExpr::Literal(NargoValue::Bool(l || r), *span, trivia.clone());
135 }
136 }
137 _ => return,
138 };
139 }
140 }
141 JsExpr::Conditional { test, consequent, alternate, span: _, trivia: _ } => {
142 Self::optimize(test);
143 Self::optimize(consequent);
144 Self::optimize(alternate);
145 if let JsExpr::Literal(test_val, _, _) = &**test {
147 if let Some(b) = test_val.as_bool() {
148 let optimized = if b { consequent.clone() } else { alternate.clone() };
149 *expr = *optimized;
150 }
151 }
152 }
153 JsExpr::Array(items, ..) => {
154 for item in items {
155 Self::optimize(item);
156 }
157 }
158 JsExpr::Object(props, ..) => {
159 for (_, value) in props {
160 Self::optimize(value);
161 }
162 }
163 JsExpr::Call { callee, args, .. } => {
164 Self::optimize(callee);
165 for arg in args {
166 Self::optimize(arg);
167 }
168 }
169 JsExpr::Member { object, property, .. } => {
170 Self::optimize(object);
171 Self::optimize(property);
172 }
173 JsExpr::OptionalMember { object, property, .. } => {
174 Self::optimize(object);
175 Self::optimize(property);
176 }
177 JsExpr::OptionalCall { callee, args, .. } => {
178 Self::optimize(callee);
179 for arg in args {
180 Self::optimize(arg);
181 }
182 }
183 JsExpr::NullishCoalescing { left, right, .. } => {
184 Self::optimize(left);
185 Self::optimize(right);
186 }
187 JsExpr::LogicalAssignment { left, right, .. } => {
188 Self::optimize(left);
189 Self::optimize(right);
190 }
191 JsExpr::ArrowFunction { body, .. } => {
192 Self::optimize(body);
193 }
194 JsExpr::TseElement { children, .. } => {
195 for child in children {
196 Self::optimize(child);
197 }
198 }
199 JsExpr::TemplateLiteral { expressions, .. } => {
200 for expr in expressions {
201 Self::optimize(expr);
202 }
203 }
204 JsExpr::Spread(expr, ..) => {
205 Self::optimize(expr);
206 }
207 JsExpr::TypeOf(expr, ..) => {
208 Self::optimize(expr);
209 }
210 JsExpr::InstanceOf { left, right, .. } => {
211 Self::optimize(left);
212 Self::optimize(right);
213 }
214 _ => {}
215 }
216 }
217}
218
219pub struct StmtOptimizer;
223
224impl StmtOptimizer {
225 pub fn optimize(stmt: &mut JsStmt) {
230 match stmt {
231 JsStmt::Expr(expr, ..) => {
232 ExprOptimizer::optimize(expr);
233 }
234 JsStmt::VariableDecl { init, .. } => {
235 if let Some(expr) = init {
236 ExprOptimizer::optimize(expr);
237 }
238 }
239 JsStmt::FunctionDecl { body, .. } => {
240 for stmt in body {
241 Self::optimize(stmt);
242 }
243 }
244 JsStmt::Return(expr, ..) => {
245 if let Some(expr) = expr {
246 ExprOptimizer::optimize(expr);
247 }
248 }
249 JsStmt::If { test, consequent, alternate, .. } => {
250 ExprOptimizer::optimize(test);
251 Self::optimize(consequent);
252 if let Some(alt) = alternate {
253 Self::optimize(alt);
254 }
255 if let JsExpr::Literal(test_val, _, _) = test {
257 if let Some(b) = test_val.as_bool() {
258 if b {
259 *stmt = *consequent.clone();
260 }
261 else if let Some(alt) = alternate {
262 *stmt = *alt.clone();
263 }
264 else {
265 *stmt = JsStmt::Block(vec![], Span::unknown(), Trivia::new());
266 }
267 }
268 }
269 }
270 JsStmt::While { test, body, .. } => {
271 ExprOptimizer::optimize(test);
272 Self::optimize(body);
273 if let JsExpr::Literal(test_val, _, _) = test {
275 if let Some(b) = test_val.as_bool() {
276 if !b {
277 *stmt = JsStmt::Block(vec![], Span::unknown(), Trivia::new());
278 }
279 }
280 }
281 }
282 JsStmt::For { init, test, update, body, .. } => {
283 if let Some(init_stmt) = init {
284 Self::optimize(init_stmt);
285 }
286 if let Some(test_expr) = test {
287 ExprOptimizer::optimize(test_expr);
288 }
289 if let Some(update_expr) = update {
290 ExprOptimizer::optimize(update_expr);
291 }
292 Self::optimize(body);
293 }
294 JsStmt::ForIn { left, right, body, .. } => {
295 Self::optimize(left);
296 ExprOptimizer::optimize(right);
297 Self::optimize(body);
298 }
299 JsStmt::ForOf { left, right, body, .. } => {
300 Self::optimize(left);
301 ExprOptimizer::optimize(right);
302 Self::optimize(body);
303 }
304 JsStmt::Try { block, handler, finalizer, .. } => {
305 Self::optimize(block);
306 if let Some((_, body)) = handler {
307 Self::optimize(body);
308 }
309 if let Some(body) = finalizer {
310 Self::optimize(body);
311 }
312 }
313 JsStmt::Switch { discriminant, cases, .. } => {
314 ExprOptimizer::optimize(discriminant);
315 for (test, stmts) in cases {
316 if let Some(test_expr) = test {
317 ExprOptimizer::optimize(test_expr);
318 }
319 for stmt in stmts {
320 Self::optimize(stmt);
321 }
322 }
323 }
324 JsStmt::Throw(expr, ..) => {
325 ExprOptimizer::optimize(expr);
326 }
327 JsStmt::Block(stmts, ..) => {
328 let mut optimized_stmts = vec![];
330 for stmt in &mut *stmts {
331 let mut optimized_stmt = stmt.clone();
332 Self::optimize(&mut optimized_stmt);
333 if let JsStmt::Block(inner_stmts, _, _) = optimized_stmt {
335 if !inner_stmts.is_empty() {
336 optimized_stmts.extend(inner_stmts);
337 }
338 }
339 else if let JsStmt::Expr(expr, _, _) = &optimized_stmt {
340 if !matches!(expr, JsExpr::Identifier(_, _, _)) {
342 optimized_stmts.push(optimized_stmt);
343 }
344 }
345 else {
346 optimized_stmts.push(optimized_stmt);
347 }
348 }
349 *stmts = optimized_stmts;
350 }
351 _ => {}
352 }
353 }
354}
355
356pub struct ProgramOptimizer;
360
361impl ProgramOptimizer {
362 pub fn optimize(program: &mut JsProgram) {
367 let mut optimized_body = vec![];
368 for stmt in &program.body {
369 let mut optimized_stmt = stmt.clone();
370 StmtOptimizer::optimize(&mut optimized_stmt);
371 if let JsStmt::Block(inner_stmts, _, _) = optimized_stmt {
373 if !inner_stmts.is_empty() {
374 optimized_body.extend(inner_stmts);
375 }
376 }
377 else {
378 optimized_body.push(optimized_stmt);
379 }
380 }
381 program.body = optimized_body;
382 }
383}
384
385pub struct IROptimizer;
389
390impl IROptimizer {
391 pub fn optimize(module: &mut IRModule) {
396 if let Some(script) = &mut module.script {
397 ProgramOptimizer::optimize(script);
398 }
399 if let Some(script_server) = &mut module.script_server {
400 ProgramOptimizer::optimize(script_server);
401 }
402 if let Some(script_client) = &mut module.script_client {
403 ProgramOptimizer::optimize(script_client);
404 }
405 }
406}