1use std::collections::HashMap;
2
3use log::debug;
4use rue_ast::{AstFunctionCallExpr, AstNode};
5use rue_diagnostic::DiagnosticKind;
6use rue_hir::{BinaryOp, Builtin, FunctionCall, Hir, Symbol, UnaryOp, Value};
7use rue_lir::ClvmOp;
8use rue_types::{Pair, Type, Union, substitute_with_mappings};
9
10use crate::{Compiler, compile_expr};
11
12pub fn compile_function_call_expr(ctx: &mut Compiler, call: &AstFunctionCallExpr) -> Value {
13 let Some(expr) = call.expr() else {
14 debug!("Unresolved function call expr");
15 return ctx.builtins().unresolved.clone();
16 };
17
18 let expr = compile_expr(ctx, &expr, None);
19
20 if let Hir::Reference(symbol) = ctx.hir(expr.hir).clone()
21 && let Symbol::Builtin(builtin) = ctx.symbol(symbol).clone()
22 {
23 return compile_builtin(ctx, call, builtin);
24 }
25
26 let expected_functions = rue_types::extract_functions(ctx.types_mut(), expr.ty);
27
28 if expected_functions.len() > 1 {
29 let name = ctx.type_name(expr.ty);
30 ctx.diagnostic(
31 call.syntax(),
32 DiagnosticKind::CannotDisambiguateFunctionTypes(name),
33 );
34 } else if expected_functions.is_empty() {
35 let name = ctx.type_name(expr.ty);
36 ctx.diagnostic(call.syntax(), DiagnosticKind::InvalidFunctionCall(name));
37 }
38
39 let expected_function = expected_functions.first();
40
41 let len = call.args().count();
42
43 let mut nil_terminated = true;
44 let mut args = Vec::new();
45
46 for (i, arg) in call.args().enumerate() {
47 if let Some(spread) = arg.spread() {
48 if i == len - 1 {
49 nil_terminated = false;
50 } else {
51 ctx.diagnostic(&spread, DiagnosticKind::NonFinalSpread);
52 }
53 }
54
55 args.extend(arg.expr());
56 }
57
58 let (args, mappings) = if let Some(function) = expected_function {
59 if function.nil_terminated != nil_terminated {
60 ctx.diagnostic(call.syntax(), DiagnosticKind::InvalidSpread);
61 }
62
63 if function.params.len() != args.len() {
64 ctx.diagnostic(
65 call.syntax(),
66 DiagnosticKind::ExpectedArguments(function.params.len(), args.len()),
67 );
68 }
69
70 let mut mappings = HashMap::new();
71 let mut results = Vec::new();
72
73 for (i, ¶m) in function.params.iter().enumerate() {
74 if let Some(expr) = args.get(i) {
75 let value = compile_expr(ctx, expr, Some(param));
76 results.push(value.hir);
77 ctx.infer_type(expr.syntax(), value.ty, param, &mut mappings);
78 } else {
79 debug!("Unresolved function call argument");
80 results.push(ctx.builtins().unresolved.hir);
81 }
82 }
83
84 (results, mappings)
85 } else {
86 (vec![], HashMap::new())
87 };
88
89 let ty = if expected_functions.is_empty() {
90 debug!("Unresolved function call return type due to unresolved function type");
91 ctx.alloc_type(Type::Unresolved)
92 } else if expected_functions.len() == 1 {
93 expected_functions[0].ret
94 } else {
95 ctx.alloc_type(Type::Union(Union::new(
96 expected_functions
97 .iter()
98 .map(|function| function.ret)
99 .collect(),
100 )))
101 };
102
103 let ty = substitute_with_mappings(ctx.types_mut(), ty, &mappings);
104
105 let hir = ctx.alloc_hir(Hir::FunctionCall(FunctionCall {
106 function: expr.hir,
107 args,
108 nil_terminated,
109 }));
110
111 Value::new(hir, ty)
112}
113
114fn compile_builtin(ctx: &mut Compiler, call: &AstFunctionCallExpr, builtin: Builtin) -> Value {
115 let mut args = Vec::new();
116 let mut spread = None;
117
118 let len = call.args().count();
119
120 for (i, arg) in call.args().enumerate() {
121 let Some(expr) = arg.expr() else {
122 debug!("Unresolved clvm op argument");
123 continue;
124 };
125
126 if let Some(op) = arg.spread() {
127 if i == len - 1 {
128 spread = Some(op);
129 } else {
130 ctx.diagnostic(arg.syntax(), DiagnosticKind::NonFinalSpread);
131 }
132 }
133
134 let value = compile_expr(ctx, &expr, None);
135
136 args.push((value, expr));
137 }
138
139 match builtin {
140 Builtin::Sha256 { inline } | Builtin::Keccak256 { inline } => {
141 if args.len() != 1 {
142 ctx.diagnostic(
143 call.syntax(),
144 DiagnosticKind::ExpectedArguments(1, args.len()),
145 );
146 }
147
148 let value = if let Some((value, expr)) = args.first() {
149 let ty = if let Some(spread) = &spread {
150 if inline {
151 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
152 }
153
154 let list = ctx.builtins().types.list;
155
156 let mappings = HashMap::from_iter([(
157 ctx.builtins().types.list_generic,
158 ctx.builtins().types.bytes,
159 )]);
160
161 rue_types::substitute_with_mappings(ctx.types_mut(), list, &mappings)
162 } else {
163 ctx.builtins().types.bytes
164 };
165 ctx.assign_type(expr.syntax(), value.ty, ty);
166 value.hir
167 } else {
168 ctx.builtins().unresolved.hir
169 };
170
171 let hir = match (builtin, spread) {
172 (Builtin::Sha256 { inline }, None) => {
173 if inline {
174 ctx.alloc_hir(Hir::Unary(UnaryOp::Sha256Inline, value))
175 } else {
176 ctx.alloc_hir(Hir::Unary(UnaryOp::Sha256, value))
177 }
178 }
179 (Builtin::Keccak256 { inline }, None) => {
180 if inline {
181 ctx.alloc_hir(Hir::Unary(UnaryOp::Keccak256Inline, value))
182 } else {
183 ctx.alloc_hir(Hir::Unary(UnaryOp::Keccak256, value))
184 }
185 }
186 (Builtin::Sha256 { .. }, Some(_)) => {
187 ctx.alloc_hir(Hir::ClvmOp(ClvmOp::Sha256, value))
188 }
189 (Builtin::Keccak256 { .. }, Some(_)) => {
190 ctx.alloc_hir(Hir::ClvmOp(ClvmOp::Keccak256, value))
191 }
192 _ => unreachable!(),
193 };
194
195 Value::new(hir, ctx.builtins().types.bytes32)
196 }
197 Builtin::Concat => {
198 if args.len() != 1 {
199 ctx.diagnostic(
200 call.syntax(),
201 DiagnosticKind::ExpectedArguments(1, args.len()),
202 );
203 }
204
205 if let Some(spread) = &spread {
206 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
207 }
208
209 let value = if let Some((value, expr)) = args.first() {
210 let list = ctx.builtins().types.list;
211
212 let mappings = HashMap::from_iter([(
213 ctx.builtins().types.list_generic,
214 ctx.builtins().types.bytes,
215 )]);
216
217 let ty = rue_types::substitute_with_mappings(ctx.types_mut(), list, &mappings);
218 ctx.assign_type(expr.syntax(), value.ty, ty);
219 value.hir
220 } else {
221 ctx.builtins().unresolved.hir
222 };
223
224 let hir = ctx.alloc_hir(Hir::ClvmOp(ClvmOp::Concat, value));
225
226 Value::new(hir, ctx.builtins().types.bytes)
227 }
228 Builtin::CoinId => {
229 if args.len() != 3 {
230 ctx.diagnostic(
231 call.syntax(),
232 DiagnosticKind::ExpectedArguments(3, args.len()),
233 );
234 }
235
236 if let Some(spread) = &spread {
237 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
238 }
239
240 let hir = if args.len() == 3 {
241 ctx.assign_type(args[0].1.syntax(), args[0].0.ty, ctx.builtins().types.bytes);
242 ctx.assign_type(args[1].1.syntax(), args[1].0.ty, ctx.builtins().types.bytes);
243 ctx.assign_type(args[2].1.syntax(), args[2].0.ty, ctx.builtins().types.int);
244 ctx.alloc_hir(Hir::CoinId(args[0].0.hir, args[1].0.hir, args[2].0.hir))
245 } else {
246 ctx.builtins().unresolved.hir
247 };
248
249 Value::new(hir, ctx.builtins().types.bytes32)
250 }
251 Builtin::Substr => {
252 if args.len() != 2 && args.len() != 3 {
253 ctx.diagnostic(
254 call.syntax(),
255 DiagnosticKind::ExpectedArgumentsBetween(2, 3, args.len()),
256 );
257 }
258
259 if let Some(spread) = &spread {
260 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
261 }
262
263 let hir = if args.len() >= 2 {
264 ctx.assign_type(args[0].1.syntax(), args[0].0.ty, ctx.builtins().types.bytes);
265 ctx.assign_type(args[1].1.syntax(), args[1].0.ty, ctx.builtins().types.int);
266
267 if args.len() == 3 {
268 ctx.assign_type(args[2].1.syntax(), args[2].0.ty, ctx.builtins().types.int);
269 }
270
271 ctx.alloc_hir(Hir::Substr(
272 args[0].0.hir,
273 args[1].0.hir,
274 args.get(2).map(|arg| arg.0.hir),
275 ))
276 } else {
277 ctx.builtins().unresolved.hir
278 };
279
280 Value::new(hir, ctx.builtins().types.bytes)
281 }
282 Builtin::Sum | Builtin::Difference | Builtin::Product => {
283 if args.len() != 1 {
284 ctx.diagnostic(
285 call.syntax(),
286 DiagnosticKind::ExpectedArguments(1, args.len()),
287 );
288 }
289
290 if let Some(spread) = &spread {
291 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
292 }
293
294 let value = if let Some((value, expr)) = args.first() {
295 let list = ctx.builtins().types.list;
296
297 let mappings = HashMap::from_iter([(
298 ctx.builtins().types.list_generic,
299 ctx.builtins().types.int,
300 )]);
301
302 let ty = rue_types::substitute_with_mappings(ctx.types_mut(), list, &mappings);
303 ctx.assign_type(expr.syntax(), value.ty, ty);
304 value.hir
305 } else {
306 ctx.builtins().unresolved.hir
307 };
308
309 let hir = ctx.alloc_hir(Hir::ClvmOp(
310 match builtin {
311 Builtin::Sum => ClvmOp::Add,
312 Builtin::Difference => ClvmOp::Sub,
313 Builtin::Product => ClvmOp::Mul,
314 _ => unreachable!(),
315 },
316 value,
317 ));
318
319 Value::new(hir, ctx.builtins().types.int)
320 }
321 Builtin::Divmod => {
322 if args.len() != 2 {
323 ctx.diagnostic(
324 call.syntax(),
325 DiagnosticKind::ExpectedArguments(2, args.len()),
326 );
327 }
328
329 if let Some(spread) = &spread {
330 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
331 }
332
333 let hir = if args.len() == 2 {
334 ctx.assign_type(args[0].1.syntax(), args[0].0.ty, ctx.builtins().types.int);
335 ctx.assign_type(args[1].1.syntax(), args[1].0.ty, ctx.builtins().types.int);
336 ctx.alloc_hir(Hir::Binary(BinaryOp::Divmod, args[0].0.hir, args[1].0.hir))
337 } else {
338 ctx.builtins().unresolved.hir
339 };
340
341 let int = ctx.builtins().types.int;
342 let pair = ctx.alloc_type(Type::Pair(Pair::new(int, int)));
343
344 Value::new(hir, pair)
345 }
346 Builtin::Modpow => {
347 if args.len() != 3 {
348 ctx.diagnostic(
349 call.syntax(),
350 DiagnosticKind::ExpectedArguments(3, args.len()),
351 );
352 }
353
354 if let Some(spread) = &spread {
355 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
356 }
357
358 let hir = if args.len() == 3 {
359 ctx.assign_type(args[0].1.syntax(), args[0].0.ty, ctx.builtins().types.int);
360 ctx.assign_type(args[1].1.syntax(), args[1].0.ty, ctx.builtins().types.int);
361 ctx.assign_type(args[2].1.syntax(), args[2].0.ty, ctx.builtins().types.int);
362 ctx.alloc_hir(Hir::Modpow(args[0].0.hir, args[1].0.hir, args[2].0.hir))
363 } else {
364 ctx.builtins().unresolved.hir
365 };
366
367 Value::new(hir, ctx.builtins().types.int)
368 }
369 Builtin::Any | Builtin::All => {
370 if args.len() != 1 {
371 ctx.diagnostic(
372 call.syntax(),
373 DiagnosticKind::ExpectedArguments(1, args.len()),
374 );
375 }
376
377 if let Some(spread) = &spread {
378 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
379 }
380
381 let value = if let Some((value, expr)) = args.first() {
382 let list = ctx.builtins().types.list;
383
384 let mappings = HashMap::from_iter([(
385 ctx.builtins().types.list_generic,
386 ctx.builtins().types.bool,
387 )]);
388
389 let ty = rue_types::substitute_with_mappings(ctx.types_mut(), list, &mappings);
390 ctx.assign_type(expr.syntax(), value.ty, ty);
391 value.hir
392 } else {
393 ctx.builtins().unresolved.hir
394 };
395
396 let hir = ctx.alloc_hir(Hir::ClvmOp(
397 match builtin {
398 Builtin::Any => ClvmOp::Any,
399 Builtin::All => ClvmOp::All,
400 _ => unreachable!(),
401 },
402 value,
403 ));
404
405 Value::new(hir, ctx.builtins().types.bool)
406 }
407 Builtin::PubkeyForExp => {
408 if args.len() != 1 {
409 ctx.diagnostic(
410 call.syntax(),
411 DiagnosticKind::ExpectedArguments(1, args.len()),
412 );
413 }
414
415 if let Some(spread) = &spread {
416 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
417 }
418
419 let hir = if args.len() == 1 {
420 ctx.assign_type(
421 args[0].1.syntax(),
422 args[0].0.ty,
423 ctx.builtins().types.bytes32,
424 );
425 ctx.alloc_hir(Hir::Unary(UnaryOp::PubkeyForExp, args[0].0.hir))
426 } else {
427 ctx.builtins().unresolved.hir
428 };
429
430 Value::new(hir, ctx.builtins().types.public_key)
431 }
432 Builtin::G1Sum | Builtin::G1Difference => {
433 if args.len() != 1 {
434 ctx.diagnostic(
435 call.syntax(),
436 DiagnosticKind::ExpectedArguments(1, args.len()),
437 );
438 }
439
440 if let Some(spread) = &spread {
441 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
442 }
443
444 let value = if let Some((value, expr)) = args.first() {
445 let list = ctx.builtins().types.list;
446
447 let mappings = HashMap::from_iter([(
448 ctx.builtins().types.list_generic,
449 ctx.builtins().types.public_key,
450 )]);
451
452 let ty = rue_types::substitute_with_mappings(ctx.types_mut(), list, &mappings);
453 ctx.assign_type(expr.syntax(), value.ty, ty);
454 value.hir
455 } else {
456 ctx.builtins().unresolved.hir
457 };
458
459 let hir = ctx.alloc_hir(Hir::ClvmOp(
460 match builtin {
461 Builtin::G1Sum => ClvmOp::G1Add,
462 Builtin::G1Difference => ClvmOp::G1Subtract,
463 _ => unreachable!(),
464 },
465 value,
466 ));
467
468 Value::new(hir, ctx.builtins().types.public_key)
469 }
470 Builtin::G2Sum | Builtin::G2Difference => {
471 if args.len() != 1 {
472 ctx.diagnostic(
473 call.syntax(),
474 DiagnosticKind::ExpectedArguments(1, args.len()),
475 );
476 }
477
478 if let Some(spread) = &spread {
479 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
480 }
481
482 let value = if let Some((value, expr)) = args.first() {
483 let list = ctx.builtins().types.list;
484
485 let mappings = HashMap::from_iter([(
486 ctx.builtins().types.list_generic,
487 ctx.builtins().types.signature,
488 )]);
489
490 let ty = rue_types::substitute_with_mappings(ctx.types_mut(), list, &mappings);
491 ctx.assign_type(expr.syntax(), value.ty, ty);
492 value.hir
493 } else {
494 ctx.builtins().unresolved.hir
495 };
496
497 let hir = ctx.alloc_hir(Hir::ClvmOp(
498 match builtin {
499 Builtin::G2Sum => ClvmOp::G2Add,
500 Builtin::G2Difference => ClvmOp::G2Subtract,
501 _ => unreachable!(),
502 },
503 value,
504 ));
505
506 Value::new(hir, ctx.builtins().types.signature)
507 }
508 Builtin::G1Map | Builtin::G2Map => {
509 if args.len() != 1 && args.len() != 2 {
510 ctx.diagnostic(
511 call.syntax(),
512 DiagnosticKind::ExpectedArgumentsBetween(1, 2, args.len()),
513 );
514 }
515
516 if let Some(spread) = &spread {
517 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
518 }
519
520 let data = if let Some((value, expr)) = args.first() {
521 ctx.assign_type(expr.syntax(), value.ty, ctx.builtins().types.bytes);
522 value.hir
523 } else {
524 ctx.builtins().unresolved.hir
525 };
526
527 let dst = args.get(1).map(|(value, expr)| {
528 ctx.assign_type(expr.syntax(), value.ty, ctx.builtins().types.bytes);
529 value.hir
530 });
531
532 let hir = match builtin {
533 Builtin::G1Map => ctx.alloc_hir(Hir::G1Map(data, dst)),
534 Builtin::G2Map => ctx.alloc_hir(Hir::G2Map(data, dst)),
535 _ => unreachable!(),
536 };
537
538 Value::new(
539 hir,
540 match builtin {
541 Builtin::G1Map => ctx.builtins().types.public_key,
542 Builtin::G2Map => ctx.builtins().types.signature,
543 _ => unreachable!(),
544 },
545 )
546 }
547 Builtin::BlsPairingIdentity => {
548 let hir = if spread.is_some() {
549 if args.len() != 1 {
550 ctx.diagnostic(
551 call.syntax(),
552 DiagnosticKind::ExpectedArguments(1, args.len()),
553 );
554 }
555
556 let list = ctx.builtins().types.alternating_list;
557
558 let mappings = HashMap::from_iter([
559 (
560 ctx.builtins().types.alternating_list_generic_a,
561 ctx.builtins().types.public_key,
562 ),
563 (
564 ctx.builtins().types.alternating_list_generic_b,
565 ctx.builtins().types.signature,
566 ),
567 ]);
568
569 let ty = rue_types::substitute_with_mappings(ctx.types_mut(), list, &mappings);
570
571 if let Some((value, expr)) = args.first() {
572 ctx.assign_type(expr.syntax(), value.ty, ty);
573 ctx.alloc_hir(Hir::ClvmOp(ClvmOp::BlsPairingIdentity, value.hir))
574 } else {
575 ctx.builtins().unresolved.hir
576 }
577 } else {
578 if args.len() % 2 != 0 {
579 ctx.diagnostic(call.syntax(), DiagnosticKind::ExpectedEvenArguments);
580 }
581
582 for (i, (value, expr)) in args.iter().enumerate() {
583 if i % 2 == 0 {
584 ctx.assign_type(expr.syntax(), value.ty, ctx.builtins().types.public_key);
585 } else {
586 ctx.assign_type(expr.syntax(), value.ty, ctx.builtins().types.signature);
587 }
588 }
589
590 ctx.alloc_hir(Hir::BlsPairingIdentity(
591 args.iter().map(|arg| arg.0.hir).collect(),
592 ))
593 };
594
595 Value::new(hir, ctx.builtins().types.nil)
596 }
597 Builtin::BlsVerify => {
598 let hir = if spread.is_some() {
599 if args.len() != 2 {
600 ctx.diagnostic(
601 call.syntax(),
602 DiagnosticKind::ExpectedArguments(2, args.len()),
603 );
604 }
605
606 let list = ctx.builtins().types.alternating_list;
607
608 let mappings = HashMap::from_iter([
609 (
610 ctx.builtins().types.alternating_list_generic_a,
611 ctx.builtins().types.public_key,
612 ),
613 (
614 ctx.builtins().types.alternating_list_generic_b,
615 ctx.builtins().types.bytes,
616 ),
617 ]);
618
619 let ty = rue_types::substitute_with_mappings(ctx.types_mut(), list, &mappings);
620
621 if args.len() == 2 {
622 ctx.assign_type(
623 args[0].1.syntax(),
624 args[0].0.ty,
625 ctx.builtins().types.signature,
626 );
627 ctx.assign_type(args[1].1.syntax(), args[1].0.ty, ty);
628
629 let pair = ctx.alloc_hir(Hir::Pair(args[0].0.hir, args[1].0.hir));
630 ctx.alloc_hir(Hir::ClvmOp(ClvmOp::BlsVerify, pair))
631 } else {
632 ctx.builtins().unresolved.hir
633 }
634 } else {
635 if args.is_empty() || args.len() % 2 != 1 {
636 ctx.diagnostic(
637 call.syntax(),
638 DiagnosticKind::ExpectedOneArgumentEvenAdditional,
639 );
640 }
641
642 for (i, (value, expr)) in args.iter().enumerate() {
643 if i == 0 {
644 ctx.assign_type(expr.syntax(), value.ty, ctx.builtins().types.signature);
645 } else if i % 2 == 1 {
646 ctx.assign_type(expr.syntax(), value.ty, ctx.builtins().types.public_key);
647 } else {
648 ctx.assign_type(expr.syntax(), value.ty, ctx.builtins().types.bytes);
649 }
650 }
651
652 if args.is_empty() {
653 ctx.builtins().unresolved.hir
654 } else {
655 ctx.alloc_hir(Hir::BlsVerify(
656 args[0].0.hir,
657 args.iter().skip(1).map(|arg| arg.0.hir).collect(),
658 ))
659 }
660 };
661
662 Value::new(hir, ctx.builtins().types.nil)
663 }
664 Builtin::Secp256K1Verify | Builtin::Secp256R1Verify => {
665 if args.len() != 3 {
666 ctx.diagnostic(
667 call.syntax(),
668 DiagnosticKind::ExpectedArguments(3, args.len()),
669 );
670 }
671
672 if let Some(spread) = &spread {
673 ctx.diagnostic(spread, DiagnosticKind::InvalidSpreadBuiltin);
674 }
675
676 let hir = if args.len() == 3 {
677 ctx.assign_type(
678 args[0].1.syntax(),
679 args[0].0.ty,
680 match builtin {
681 Builtin::Secp256K1Verify => ctx.builtins().types.k1_public_key,
682 Builtin::Secp256R1Verify => ctx.builtins().types.r1_public_key,
683 _ => unreachable!(),
684 },
685 );
686 ctx.assign_type(
687 args[1].1.syntax(),
688 args[1].0.ty,
689 ctx.builtins().types.bytes32,
690 );
691 ctx.assign_type(
692 args[2].1.syntax(),
693 args[2].0.ty,
694 match builtin {
695 Builtin::Secp256K1Verify => ctx.builtins().types.k1_signature,
696 Builtin::Secp256R1Verify => ctx.builtins().types.r1_signature,
697 _ => unreachable!(),
698 },
699 );
700
701 match builtin {
702 Builtin::Secp256K1Verify => ctx.alloc_hir(Hir::Secp256K1Verify(
703 args[0].0.hir,
704 args[1].0.hir,
705 args[2].0.hir,
706 )),
707 Builtin::Secp256R1Verify => ctx.alloc_hir(Hir::Secp256R1Verify(
708 args[0].0.hir,
709 args[1].0.hir,
710 args[2].0.hir,
711 )),
712 _ => unreachable!(),
713 }
714 } else {
715 ctx.builtins().unresolved.hir
716 };
717
718 Value::new(hir, ctx.builtins().types.nil)
719 }
720 }
721}