1use super::CodeGeneratingVisitor;
18
19use leo_ast::{
20 ArrayAccess,
21 ArrayExpression,
22 AssociatedConstantExpression,
23 AssociatedFunctionExpression,
24 BinaryExpression,
25 BinaryOperation,
26 CallExpression,
27 CastExpression,
28 ErrExpression,
29 Expression,
30 Identifier,
31 Literal,
32 LiteralVariant,
33 Location,
34 LocatorExpression,
35 MemberAccess,
36 Node,
37 RepeatExpression,
38 StructExpression,
39 TernaryExpression,
40 TupleExpression,
41 Type,
42 UnaryExpression,
43 UnaryOperation,
44 UnitExpression,
45 Variant,
46};
47use leo_span::sym;
48
49use std::fmt::Write as _;
50
51impl CodeGeneratingVisitor<'_> {
53 pub fn visit_expression(&mut self, input: &Expression) -> (String, String) {
54 match input {
55 Expression::Array(expr) => self.visit_array(expr),
56 Expression::ArrayAccess(expr) => self.visit_array_access(expr),
57 Expression::AssociatedConstant(expr) => self.visit_associated_constant(expr),
58 Expression::AssociatedFunction(expr) => self.visit_associated_function(expr),
59 Expression::Binary(expr) => self.visit_binary(expr),
60 Expression::Call(expr) => self.visit_call(expr),
61 Expression::Cast(expr) => self.visit_cast(expr),
62 Expression::Struct(expr) => self.visit_struct_init(expr),
63 Expression::Err(expr) => self.visit_err(expr),
64 Expression::Identifier(expr) => self.visit_identifier(expr),
65 Expression::Literal(expr) => self.visit_value(expr),
66 Expression::Locator(expr) => self.visit_locator(expr),
67 Expression::MemberAccess(expr) => self.visit_member_access(expr),
68 Expression::Repeat(expr) => self.visit_repeat(expr),
69 Expression::Ternary(expr) => self.visit_ternary(expr),
70 Expression::Tuple(expr) => self.visit_tuple(expr),
71 Expression::TupleAccess(_) => panic!("Tuple accesses should not appear in the AST at this point."),
72 Expression::Unary(expr) => self.visit_unary(expr),
73 Expression::Unit(expr) => self.visit_unit(expr),
74 }
75 }
76
77 fn visit_identifier(&mut self, input: &Identifier) -> (String, String) {
78 (
79 self.variable_mapping.get(&input.name).or_else(|| self.global_mapping.get(&input.name)).unwrap().clone(),
80 String::new(),
81 )
82 }
83
84 fn visit_err(&mut self, _input: &ErrExpression) -> (String, String) {
85 panic!("`ErrExpression`s should not be in the AST at this phase of compilation.")
86 }
87
88 fn visit_value(&mut self, input: &Literal) -> (String, String) {
89 let literal = if let LiteralVariant::Unsuffixed(value) = &input.variant {
91 match self.state.type_table.get(&input.id) {
94 Some(Type::Integer(int_ty)) => Literal {
95 variant: LiteralVariant::Integer(int_ty, value.clone()),
96 id: self.state.node_builder.next_id(),
97 span: input.span,
98 },
99 Some(Type::Field) => Literal {
100 variant: LiteralVariant::Field(value.clone()),
101 id: self.state.node_builder.next_id(),
102 span: input.span,
103 },
104 Some(Type::Group) => Literal {
105 variant: LiteralVariant::Group(value.clone()),
106 id: self.state.node_builder.next_id(),
107 span: input.span,
108 },
109 Some(Type::Scalar) => Literal {
110 variant: LiteralVariant::Scalar(value.clone()),
111 id: self.state.node_builder.next_id(),
112 span: input.span,
113 },
114 _ => panic!(
115 "Unexpected type for unsuffixed integer literal. This should have been caught by the type checker"
116 ),
117 }
118 } else {
119 input.clone()
120 };
121 (format!("{}", literal.display_decimal()), String::new())
122 }
123
124 fn visit_locator(&mut self, input: &LocatorExpression) -> (String, String) {
125 if input.program.name.name == self.program_id.expect("Locators only appear within programs.").name.name {
126 (format!("{}", input.name), String::new())
128 } else {
129 (format!("{input}"), String::new())
130 }
131 }
132
133 fn visit_binary(&mut self, input: &BinaryExpression) -> (String, String) {
134 let (left_operand, left_instructions) = self.visit_expression(&input.left);
135 let (right_operand, right_instructions) = self.visit_expression(&input.right);
136
137 let opcode = match input.op {
138 BinaryOperation::Add => String::from("add"),
139 BinaryOperation::AddWrapped => String::from("add.w"),
140 BinaryOperation::And => String::from("and"),
141 BinaryOperation::BitwiseAnd => String::from("and"),
142 BinaryOperation::Div => String::from("div"),
143 BinaryOperation::DivWrapped => String::from("div.w"),
144 BinaryOperation::Eq => String::from("is.eq"),
145 BinaryOperation::Gte => String::from("gte"),
146 BinaryOperation::Gt => String::from("gt"),
147 BinaryOperation::Lte => String::from("lte"),
148 BinaryOperation::Lt => String::from("lt"),
149 BinaryOperation::Mod => String::from("mod"),
150 BinaryOperation::Mul => String::from("mul"),
151 BinaryOperation::MulWrapped => String::from("mul.w"),
152 BinaryOperation::Nand => String::from("nand"),
153 BinaryOperation::Neq => String::from("is.neq"),
154 BinaryOperation::Nor => String::from("nor"),
155 BinaryOperation::Or => String::from("or"),
156 BinaryOperation::BitwiseOr => String::from("or"),
157 BinaryOperation::Pow => String::from("pow"),
158 BinaryOperation::PowWrapped => String::from("pow.w"),
159 BinaryOperation::Rem => String::from("rem"),
160 BinaryOperation::RemWrapped => String::from("rem.w"),
161 BinaryOperation::Shl => String::from("shl"),
162 BinaryOperation::ShlWrapped => String::from("shl.w"),
163 BinaryOperation::Shr => String::from("shr"),
164 BinaryOperation::ShrWrapped => String::from("shr.w"),
165 BinaryOperation::Sub => String::from("sub"),
166 BinaryOperation::SubWrapped => String::from("sub.w"),
167 BinaryOperation::Xor => String::from("xor"),
168 };
169
170 let destination_register = self.next_register();
171 let binary_instruction = format!(" {opcode} {left_operand} {right_operand} into {destination_register};\n",);
172
173 let mut instructions = left_instructions;
175 instructions.push_str(&right_instructions);
176 instructions.push_str(&binary_instruction);
177
178 (destination_register, instructions)
179 }
180
181 fn visit_cast(&mut self, input: &CastExpression) -> (String, String) {
182 let (expression_operand, mut instructions) = self.visit_expression(&input.expression);
183
184 let destination_register = self.next_register();
186
187 let cast_instruction = format!(
188 " cast {expression_operand} into {destination_register} as {};\n",
189 Self::visit_type(&input.type_)
190 );
191
192 instructions.push_str(&cast_instruction);
194
195 (destination_register, instructions)
196 }
197
198 fn visit_array(&mut self, input: &ArrayExpression) -> (String, String) {
199 let mut expression_operands = String::new();
200 let mut instructions = String::new();
201 for (operand, operand_instructions) in input.elements.iter().map(|expr| self.visit_expression(expr)) {
202 let space = if expression_operands.is_empty() { "" } else { " " };
203 write!(&mut expression_operands, "{space}{operand}").unwrap();
204 instructions.push_str(&operand_instructions);
205 }
206
207 let destination_register = self.next_register();
209
210 let Some(array_type @ Type::Array(..)) = self.state.type_table.get(&input.id) else {
212 panic!("All types should be known at this phase of compilation");
213 };
214 let array_type: String = Self::visit_type(&array_type);
215
216 let array_instruction =
217 format!(" cast {expression_operands} into {destination_register} as {};\n", array_type);
218
219 instructions.push_str(&array_instruction);
221
222 (destination_register, instructions)
223 }
224
225 fn visit_unary(&mut self, input: &UnaryExpression) -> (String, String) {
226 let (expression_operand, expression_instructions) = self.visit_expression(&input.receiver);
227
228 let (opcode, suffix) = match input.op {
230 UnaryOperation::Abs => ("abs", ""),
231 UnaryOperation::AbsWrapped => ("abs.w", ""),
232 UnaryOperation::Double => ("double", ""),
233 UnaryOperation::Inverse => ("inv", ""),
234 UnaryOperation::Not => ("not", ""),
235 UnaryOperation::Negate => ("neg", ""),
236 UnaryOperation::Square => ("square", ""),
237 UnaryOperation::SquareRoot => ("sqrt", ""),
238 UnaryOperation::ToXCoordinate => ("cast", " as group.x"),
239 UnaryOperation::ToYCoordinate => ("cast", " as group.y"),
240 };
241
242 let destination_register = self.next_register();
243 let unary_instruction = format!(" {opcode} {expression_operand} into {destination_register}{suffix};\n");
244
245 let mut instructions = expression_instructions;
247 instructions.push_str(&unary_instruction);
248
249 (destination_register, instructions)
250 }
251
252 fn visit_ternary(&mut self, input: &TernaryExpression) -> (String, String) {
253 let (condition_operand, condition_instructions) = self.visit_expression(&input.condition);
254 let (if_true_operand, if_true_instructions) = self.visit_expression(&input.if_true);
255 let (if_false_operand, if_false_instructions) = self.visit_expression(&input.if_false);
256
257 let destination_register = self.next_register();
258 let ternary_instruction = format!(
259 " ternary {condition_operand} {if_true_operand} {if_false_operand} into {destination_register};\n",
260 );
261
262 let mut instructions = condition_instructions;
264 instructions.push_str(&if_true_instructions);
265 instructions.push_str(&if_false_instructions);
266 instructions.push_str(&ternary_instruction);
267
268 (destination_register, instructions)
269 }
270
271 fn visit_struct_init(&mut self, input: &StructExpression) -> (String, String) {
272 let name = if let Some((is_record, type_)) = self.composite_mapping.get(&input.name.name) {
274 if *is_record {
275 format!("{}.{type_}", input.name)
277 } else {
278 input.name.to_string()
280 }
281 } else {
282 panic!("All composite types should be known at this phase of compilation")
283 };
284
285 let mut instructions = String::new();
287 let mut struct_init_instruction = String::from(" cast ");
288
289 for member in input.members.iter() {
291 let operand = if let Some(expr) = member.expression.as_ref() {
292 let (variable_operand, variable_instructions) = self.visit_expression(expr);
294 instructions.push_str(&variable_instructions);
295
296 variable_operand
297 } else {
298 let (ident_operand, ident_instructions) = self.visit_identifier(&member.identifier);
300 instructions.push_str(&ident_instructions);
301
302 ident_operand
303 };
304
305 write!(struct_init_instruction, "{operand} ").expect("failed to write to string");
307 }
308
309 let destination_register = self.next_register();
311 writeln!(struct_init_instruction, "into {destination_register} as {name};",)
312 .expect("failed to write to string");
313
314 instructions.push_str(&struct_init_instruction);
315
316 (destination_register, instructions)
317 }
318
319 fn visit_array_access(&mut self, input: &ArrayAccess) -> (String, String) {
320 let (array_operand, _) = self.visit_expression(&input.array);
321
322 assert!(
323 matches!(self.state.type_table.get(&input.index.id()), Some(Type::Integer(_))),
324 "unexpected type for for array index. This should have been caught by the type checker."
325 );
326
327 let index_operand = match &input.index {
328 Expression::Literal(Literal {
329 variant: LiteralVariant::Integer(_, s) | LiteralVariant::Unsuffixed(s),
330 ..
331 }) => format!("{s}u32"),
332 _ => panic!("Array indices must be integer literals"),
333 };
334
335 (format!("{array_operand}[{index_operand}]"), String::new())
336 }
337
338 fn visit_member_access(&mut self, input: &MemberAccess) -> (String, String) {
339 let (inner_expr, _) = self.visit_expression(&input.inner);
340 let member_access = format!("{}.{}", inner_expr, input.name);
341
342 (member_access, String::new())
343 }
344
345 fn visit_repeat(&mut self, input: &RepeatExpression) -> (String, String) {
346 let (operand, mut operand_instructions) = self.visit_expression(&input.expr);
347 let count = input.count.as_u32().expect("repeat count should be known at this point");
348
349 let expression_operands = std::iter::repeat(operand).take(count as usize).collect::<Vec<_>>().join(" ");
350
351 let destination_register = self.next_register();
353
354 let Some(array_type @ Type::Array(..)) = self.state.type_table.get(&input.id) else {
356 panic!("All types should be known at this phase of compilation");
357 };
358 let array_type: String = Self::visit_type(&array_type);
359
360 let array_instruction =
361 format!(" cast {expression_operands} into {destination_register} as {};\n", array_type);
362
363 operand_instructions.push_str(&array_instruction);
365
366 (destination_register, operand_instructions)
367 }
368
369 fn visit_associated_constant(&mut self, input: &AssociatedConstantExpression) -> (String, String) {
371 (format!("{input}"), String::new())
372 }
373
374 fn visit_associated_function(&mut self, input: &AssociatedFunctionExpression) -> (String, String) {
376 let mut instructions = String::new();
377
378 let arguments = input
380 .arguments
381 .iter()
382 .map(|argument| {
383 let (arg_string, arg_instructions) = self.visit_expression(argument);
384 instructions.push_str(&arg_instructions);
385 arg_string
386 })
387 .collect::<Vec<_>>();
388
389 let mut construct_simple_function_call = |function: &Identifier, variant: &str, arguments: Vec<String>| {
392 let function_name = function.name.to_string();
394 let mut names = function_name.split("_to_");
395 let opcode = names.next().expect("failed to get opcode");
396 let return_type = names.next().expect("failed to get type");
397
398 let mut instruction = format!(" {opcode}.{variant}");
399 for argument in arguments {
400 write!(instruction, " {argument}").expect("failed to write to string");
401 }
402 let destination_register = self.next_register();
403 writeln!(instruction, " into {destination_register} as {return_type};").expect("failed to write to string");
404 (destination_register, instruction)
405 };
406
407 let (destination, instruction) = match input.variant.name {
409 sym::BHP256 => construct_simple_function_call(&input.name, "bhp256", arguments),
410 sym::BHP512 => construct_simple_function_call(&input.name, "bhp512", arguments),
411 sym::BHP768 => construct_simple_function_call(&input.name, "bhp768", arguments),
412 sym::BHP1024 => construct_simple_function_call(&input.name, "bhp1024", arguments),
413 sym::Keccak256 => construct_simple_function_call(&input.name, "keccak256", arguments),
414 sym::Keccak384 => construct_simple_function_call(&input.name, "keccak384", arguments),
415 sym::Keccak512 => construct_simple_function_call(&input.name, "keccak512", arguments),
416 sym::Pedersen64 => construct_simple_function_call(&input.name, "ped64", arguments),
417 sym::Pedersen128 => construct_simple_function_call(&input.name, "ped128", arguments),
418 sym::Poseidon2 => construct_simple_function_call(&input.name, "psd2", arguments),
419 sym::Poseidon4 => construct_simple_function_call(&input.name, "psd4", arguments),
420 sym::Poseidon8 => construct_simple_function_call(&input.name, "psd8", arguments),
421 sym::SHA3_256 => construct_simple_function_call(&input.name, "sha3_256", arguments),
422 sym::SHA3_384 => construct_simple_function_call(&input.name, "sha3_384", arguments),
423 sym::SHA3_512 => construct_simple_function_call(&input.name, "sha3_512", arguments),
424 sym::Mapping => match input.name.name {
425 sym::get => {
426 let mut instruction = " get".to_string();
427 let destination_register = self.next_register();
428 writeln!(instruction, " {}[{}] into {destination_register};", arguments[0], arguments[1])
430 .expect("failed to write to string");
431 (destination_register, instruction)
432 }
433 sym::get_or_use => {
434 let mut instruction = " get.or_use".to_string();
435 let destination_register = self.next_register();
436 writeln!(
438 instruction,
439 " {}[{}] {} into {destination_register};",
440 arguments[0], arguments[1], arguments[2]
441 )
442 .expect("failed to write to string");
443 (destination_register, instruction)
444 }
445 sym::set => {
446 let mut instruction = " set".to_string();
447 writeln!(instruction, " {} into {}[{}];", arguments[2], arguments[0], arguments[1])
449 .expect("failed to write to string");
450 (String::new(), instruction)
451 }
452 sym::remove => {
453 let mut instruction = " remove".to_string();
454 writeln!(instruction, " {}[{}];", arguments[0], arguments[1]).expect("failed to write to string");
456 (String::new(), instruction)
457 }
458 sym::contains => {
459 let mut instruction = " contains".to_string();
460 let destination_register = self.next_register();
461 writeln!(instruction, " {}[{}] into {destination_register};", arguments[0], arguments[1])
463 .expect("failed to write to string");
464 (destination_register, instruction)
465 }
466 _ => panic!("The only variants of Mapping are get, get_or, and set"),
467 },
468 sym::group => {
469 match input.name {
470 Identifier { name: sym::to_x_coordinate, .. } => {
471 let mut instruction = " cast".to_string();
472 let destination_register = self.next_register();
473 writeln!(instruction, " {} into {destination_register} as group.x;", arguments[0],)
475 .expect("failed to write to string");
476 (destination_register, instruction)
477 }
478 Identifier { name: sym::to_y_coordinate, .. } => {
479 let mut instruction = " cast".to_string();
480 let destination_register = self.next_register();
481 writeln!(instruction, " {} into {destination_register} as group.y;", arguments[0],)
483 .expect("failed to write to string");
484 (destination_register, instruction)
485 }
486 _ => panic!("The only associated methods of group are to_x_coordinate and to_y_coordinate"),
487 }
488 }
489 sym::ChaCha => {
490 let destination_register = self.next_register();
492 let mut instruction = format!(" rand.chacha into {destination_register} as ");
494 match input.name {
496 Identifier { name: sym::rand_address, .. } => writeln!(instruction, "address;"),
497 Identifier { name: sym::rand_bool, .. } => writeln!(instruction, "boolean;"),
498 Identifier { name: sym::rand_field, .. } => writeln!(instruction, "field;"),
499 Identifier { name: sym::rand_group, .. } => writeln!(instruction, "group;"),
500 Identifier { name: sym::rand_i8, .. } => writeln!(instruction, "i8;"),
501 Identifier { name: sym::rand_i16, .. } => writeln!(instruction, "i16;"),
502 Identifier { name: sym::rand_i32, .. } => writeln!(instruction, "i32;"),
503 Identifier { name: sym::rand_i64, .. } => writeln!(instruction, "i64;"),
504 Identifier { name: sym::rand_i128, .. } => writeln!(instruction, "i128;"),
505 Identifier { name: sym::rand_scalar, .. } => writeln!(instruction, "scalar;"),
506 Identifier { name: sym::rand_u8, .. } => writeln!(instruction, "u8;"),
507 Identifier { name: sym::rand_u16, .. } => writeln!(instruction, "u16;"),
508 Identifier { name: sym::rand_u32, .. } => writeln!(instruction, "u32;"),
509 Identifier { name: sym::rand_u64, .. } => writeln!(instruction, "u64;"),
510 Identifier { name: sym::rand_u128, .. } => writeln!(instruction, "u128;"),
511 _ => panic!("The only associated methods of ChaCha are `rand_*`"),
512 }
513 .expect("failed to write to string");
514 (destination_register, instruction)
515 }
516 sym::signature => {
517 let mut instruction = " sign.verify".to_string();
518 let destination_register = self.next_register();
519 writeln!(
521 instruction,
522 " {} {} {} into {destination_register};",
523 arguments[0], arguments[1], arguments[2]
524 )
525 .expect("failed to write to string");
526 (destination_register, instruction)
527 }
528 sym::Future => {
529 let mut instruction = " await".to_string();
530 writeln!(instruction, " {};", arguments[0]).expect("failed to write to string");
531 (String::new(), instruction)
532 }
533 sym::CheatCode => {
534 (String::new(), String::new())
535 }
537 _ => {
538 panic!("All core functions should be known at this phase of compilation")
539 }
540 };
541 instructions.push_str(&instruction);
543
544 (destination, instructions)
545 }
546
547 fn visit_call(&mut self, input: &CallExpression) -> (String, String) {
548 let caller_program = self.program_id.expect("Calls only appear within programs.").name.name;
549 let callee_program = input.program.unwrap_or(caller_program);
550
551 let func_symbol = self
552 .state
553 .symbol_table
554 .lookup_function(Location::new(callee_program, input.function.name))
555 .expect("Type checking guarantees functions exist");
556
557 let mut call_instruction = if caller_program != callee_program {
559 assert!(
561 self.program.stubs.get(&callee_program).is_some(),
562 "Type checking guarantees that imported and stub programs are present."
563 );
564 format!(" call {}.aleo/{}", callee_program, input.function)
565 } else if func_symbol.function.variant.is_async() {
566 format!(" async {}", self.current_function.unwrap().identifier)
567 } else {
568 format!(" call {}", input.function)
569 };
570
571 let mut instructions = String::new();
572
573 for argument in input.arguments.iter() {
574 let (argument, argument_instructions) = self.visit_expression(argument);
575 write!(call_instruction, " {argument}").expect("failed to write to string");
576 instructions.push_str(&argument_instructions);
577 }
578
579 let mut destinations = Vec::new();
581
582 match func_symbol.function.output_type.clone() {
584 Type::Unit => {} Type::Tuple(tuple) => match tuple.length() {
586 0 | 1 => panic!("Parsing guarantees that a tuple type has at least two elements"),
587 len => {
588 for _ in 0..len {
589 destinations.push(self.next_register());
590 }
591 }
592 },
593 _ => {
594 destinations.push(self.next_register());
595 }
596 }
597
598 if func_symbol.function.variant == Variant::AsyncFunction {
600 destinations.push(self.next_register());
601 }
602
603 let output_operands = destinations.join(" ");
605
606 if !destinations.is_empty() {
608 write!(call_instruction, " into").expect("failed to write to string");
609 for destination in &destinations {
610 write!(call_instruction, " {}", destination).expect("failed to write to string");
611 }
612 }
613
614 writeln!(call_instruction, ";").expect("failed to write to string");
616
617 instructions.push_str(&call_instruction);
619
620 (output_operands, instructions)
622 }
623
624 fn visit_tuple(&mut self, input: &TupleExpression) -> (String, String) {
625 let mut tuple_elements = Vec::with_capacity(input.elements.len());
628 let mut instructions = String::new();
629
630 for element in input.elements.iter() {
632 let (element, element_instructions) = self.visit_expression(element);
633 tuple_elements.push(element);
634 instructions.push_str(&element_instructions);
635 }
636
637 (tuple_elements.join(" "), instructions)
639 }
640
641 fn visit_unit(&mut self, _input: &UnitExpression) -> (String, String) {
642 panic!("`UnitExpression`s should not be visited during code generation.")
643 }
644
645 pub fn clone_register(&mut self, register: &str, typ: &Type) -> (String, String) {
646 let new_reg = self.next_register();
647 match typ {
648 Type::Address
649 | Type::Boolean
650 | Type::Field
651 | Type::Group
652 | Type::Scalar
653 | Type::Signature
654 | Type::Integer(_) => {
655 let instruction = format!(" cast {register} into {new_reg} as {typ};\n");
657 (new_reg, instruction)
658 }
659
660 Type::Array(array_type) => {
661 let mut instruction = " cast ".to_string();
663 for i in 0..array_type.length.as_u32().expect("length should be known at this point") as usize {
664 write!(&mut instruction, "{register}[{i}u32] ").unwrap();
665 }
666 writeln!(&mut instruction, "into {new_reg} as {};", Self::visit_type(typ)).unwrap();
667 (new_reg, instruction)
668 }
669
670 Type::Composite(comp_ty) => {
671 let program = comp_ty.program.unwrap_or(self.program_id.unwrap().name.name);
673 let location = Location::new(program, comp_ty.id.name);
674 let comp = self
675 .state
676 .symbol_table
677 .lookup_record(location)
678 .or_else(|| self.state.symbol_table.lookup_struct(comp_ty.id.name))
679 .unwrap();
680 let mut instruction = " cast ".to_string();
681 for member in &comp.members {
682 write!(&mut instruction, "{register}.{} ", member.identifier.name).unwrap();
683 }
684 writeln!(
685 &mut instruction,
686 "into {new_reg} as {};",
687 self.visit_type_with_visibility(typ, leo_ast::Mode::None)
689 )
690 .unwrap();
691 (new_reg, instruction)
692 }
693
694 Type::Mapping(..)
695 | Type::Future(..)
696 | Type::Tuple(..)
697 | Type::Identifier(..)
698 | Type::String
699 | Type::Unit
700 | Type::Numeric
701 | Type::Err => panic!("Objects of type {typ} cannot be cloned."),
702 }
703 }
704}