use crate::CodeGenerator;
use leo_ast::{
AccessExpression,
ArrayAccess,
ArrayExpression,
AssociatedConstant,
AssociatedFunction,
BinaryExpression,
BinaryOperation,
CallExpression,
CastExpression,
ErrExpression,
Expression,
Identifier,
Literal,
Location,
LocatorExpression,
MemberAccess,
StructExpression,
TernaryExpression,
TupleExpression,
Type,
UnaryExpression,
UnaryOperation,
UnitExpression,
Variant,
};
use leo_span::sym;
use std::borrow::Borrow;
use std::fmt::Write as _;
impl<'a> CodeGenerator<'a> {
pub(crate) fn visit_expression(&mut self, input: &'a Expression) -> (String, String) {
match input {
Expression::Access(expr) => self.visit_access(expr),
Expression::Array(expr) => self.visit_array(expr),
Expression::Binary(expr) => self.visit_binary(expr),
Expression::Call(expr) => self.visit_call(expr),
Expression::Cast(expr) => self.visit_cast(expr),
Expression::Struct(expr) => self.visit_struct_init(expr),
Expression::Err(expr) => self.visit_err(expr),
Expression::Identifier(expr) => self.visit_identifier(expr),
Expression::Literal(expr) => self.visit_value(expr),
Expression::Locator(expr) => self.visit_locator(expr),
Expression::Ternary(expr) => self.visit_ternary(expr),
Expression::Tuple(expr) => self.visit_tuple(expr),
Expression::Unary(expr) => self.visit_unary(expr),
Expression::Unit(expr) => self.visit_unit(expr),
}
}
fn visit_identifier(&mut self, input: &'a Identifier) -> (String, String) {
(
self.variable_mapping.get(&input.name).or_else(|| self.global_mapping.get(&input.name)).unwrap().clone(),
String::new(),
)
}
fn visit_err(&mut self, _input: &'a ErrExpression) -> (String, String) {
unreachable!("`ErrExpression`s should not be in the AST at this phase of compilation.")
}
fn visit_value(&mut self, input: &'a Literal) -> (String, String) {
let decimal_input = input.display_decimal();
(format!("{decimal_input}"), String::new())
}
fn visit_locator(&mut self, input: &'a LocatorExpression) -> (String, String) {
if input.program.name.name == self.program_id.expect("Locators only appear within programs.").name.name {
(format!("{}", input.name), String::new())
} else {
(format!("{input}"), String::new())
}
}
fn visit_binary(&mut self, input: &'a BinaryExpression) -> (String, String) {
let (left_operand, left_instructions) = self.visit_expression(&input.left);
let (right_operand, right_instructions) = self.visit_expression(&input.right);
let opcode = match input.op {
BinaryOperation::Add => String::from("add"),
BinaryOperation::AddWrapped => String::from("add.w"),
BinaryOperation::And => String::from("and"),
BinaryOperation::BitwiseAnd => String::from("and"),
BinaryOperation::Div => String::from("div"),
BinaryOperation::DivWrapped => String::from("div.w"),
BinaryOperation::Eq => String::from("is.eq"),
BinaryOperation::Gte => String::from("gte"),
BinaryOperation::Gt => String::from("gt"),
BinaryOperation::Lte => String::from("lte"),
BinaryOperation::Lt => String::from("lt"),
BinaryOperation::Mod => String::from("mod"),
BinaryOperation::Mul => String::from("mul"),
BinaryOperation::MulWrapped => String::from("mul.w"),
BinaryOperation::Nand => String::from("nand"),
BinaryOperation::Neq => String::from("is.neq"),
BinaryOperation::Nor => String::from("nor"),
BinaryOperation::Or => String::from("or"),
BinaryOperation::BitwiseOr => String::from("or"),
BinaryOperation::Pow => String::from("pow"),
BinaryOperation::PowWrapped => String::from("pow.w"),
BinaryOperation::Rem => String::from("rem"),
BinaryOperation::RemWrapped => String::from("rem.w"),
BinaryOperation::Shl => String::from("shl"),
BinaryOperation::ShlWrapped => String::from("shl.w"),
BinaryOperation::Shr => String::from("shr"),
BinaryOperation::ShrWrapped => String::from("shr.w"),
BinaryOperation::Sub => String::from("sub"),
BinaryOperation::SubWrapped => String::from("sub.w"),
BinaryOperation::Xor => String::from("xor"),
};
let destination_register = format!("r{}", self.next_register);
let binary_instruction = format!(" {opcode} {left_operand} {right_operand} into {destination_register};\n",);
self.next_register += 1;
let mut instructions = left_instructions;
instructions.push_str(&right_instructions);
instructions.push_str(&binary_instruction);
(destination_register, instructions)
}
fn visit_cast(&mut self, input: &'a CastExpression) -> (String, String) {
let (expression_operand, mut instructions) = self.visit_expression(&input.expression);
let destination_register = format!("r{}", self.next_register);
self.next_register += 1;
let cast_instruction = format!(
" cast {expression_operand} into {destination_register} as {};\n",
Self::visit_type(&input.type_)
);
instructions.push_str(&cast_instruction);
(destination_register, instructions)
}
fn visit_array(&mut self, input: &'a ArrayExpression) -> (String, String) {
let (expression_operands, mut instructions) =
input.elements.iter().map(|expr| self.visit_expression(expr)).fold(
(String::new(), String::new()),
|(mut operands, mut instructions), (operand, operand_instructions)| {
operands.push_str(&format!(" {operand}"));
instructions.push_str(&operand_instructions);
(operands, instructions)
},
);
let destination_register = format!("r{}", self.next_register);
self.next_register += 1;
let array_type = match self.type_table.get(&input.id) {
Some(Type::Array(array_type)) => Type::Array(array_type),
_ => unreachable!("All types should be known at this phase of compilation"),
};
let array_type: String = Self::visit_type(&array_type);
let array_instruction =
format!(" cast {expression_operands} into {destination_register} as {};\n", array_type);
instructions.push_str(&array_instruction);
(destination_register, instructions)
}
fn visit_unary(&mut self, input: &'a UnaryExpression) -> (String, String) {
let (expression_operand, expression_instructions) = self.visit_expression(&input.receiver);
let (opcode, suffix) = match input.op {
UnaryOperation::Abs => ("abs", ""),
UnaryOperation::AbsWrapped => ("abs.w", ""),
UnaryOperation::Double => ("double", ""),
UnaryOperation::Inverse => ("inv", ""),
UnaryOperation::Not => ("not", ""),
UnaryOperation::Negate => ("neg", ""),
UnaryOperation::Square => ("square", ""),
UnaryOperation::SquareRoot => ("sqrt", ""),
UnaryOperation::ToXCoordinate => ("cast", " as group.x"),
UnaryOperation::ToYCoordinate => ("cast", " as group.y"),
};
let destination_register = format!("r{}", self.next_register);
let unary_instruction = format!(" {opcode} {expression_operand} into {destination_register}{suffix};\n");
self.next_register += 1;
let mut instructions = expression_instructions;
instructions.push_str(&unary_instruction);
(destination_register, instructions)
}
fn visit_ternary(&mut self, input: &'a TernaryExpression) -> (String, String) {
let (condition_operand, condition_instructions) = self.visit_expression(&input.condition);
let (if_true_operand, if_true_instructions) = self.visit_expression(&input.if_true);
let (if_false_operand, if_false_instructions) = self.visit_expression(&input.if_false);
let destination_register = format!("r{}", self.next_register);
let ternary_instruction = format!(
" ternary {condition_operand} {if_true_operand} {if_false_operand} into {destination_register};\n",
);
self.next_register += 1;
let mut instructions = condition_instructions;
instructions.push_str(&if_true_instructions);
instructions.push_str(&if_false_instructions);
instructions.push_str(&ternary_instruction);
(destination_register, instructions)
}
fn visit_struct_init(&mut self, input: &'a StructExpression) -> (String, String) {
let name = if let Some((is_record, type_)) = self.composite_mapping.get(&input.name.name) {
if *is_record {
format!("{}.{type_}", input.name)
} else {
input.name.to_string()
}
} else {
unreachable!("All composite types should be known at this phase of compilation")
};
let mut instructions = String::new();
let mut struct_init_instruction = String::from(" cast ");
for member in input.members.iter() {
let operand = if let Some(expr) = member.expression.as_ref() {
let (variable_operand, variable_instructions) = self.visit_expression(expr);
instructions.push_str(&variable_instructions);
variable_operand
} else {
let (ident_operand, ident_instructions) = self.visit_identifier(&member.identifier);
instructions.push_str(&ident_instructions);
ident_operand
};
write!(struct_init_instruction, "{operand} ").expect("failed to write to string");
}
let destination_register = format!("r{}", self.next_register);
writeln!(struct_init_instruction, "into {destination_register} as {name};",)
.expect("failed to write to string");
instructions.push_str(&struct_init_instruction);
self.next_register += 1;
(destination_register, instructions)
}
fn visit_array_access(&mut self, input: &'a ArrayAccess) -> (String, String) {
let (array_operand, _) = self.visit_expression(&input.array);
let index_operand = match input.index.as_ref() {
Expression::Literal(Literal::Integer(_, string, _, _)) => format!("{}u32", string),
_ => unreachable!("Array indices must be integer literals"),
};
let array_access = format!("{}[{}]", array_operand, index_operand);
(array_access, String::new())
}
fn visit_member_access(&mut self, input: &'a MemberAccess) -> (String, String) {
let (inner_expr, _) = self.visit_expression(&input.inner);
let member_access = format!("{}.{}", inner_expr, input.name);
(member_access, String::new())
}
fn visit_associated_constant(&mut self, input: &'a AssociatedConstant) -> (String, String) {
(format!("{input}"), String::new())
}
fn visit_associated_function(&mut self, input: &'a AssociatedFunction) -> (String, String) {
let mut instructions = String::new();
let arguments = input
.arguments
.iter()
.map(|argument| {
let (arg_string, arg_instructions) = self.visit_expression(argument);
instructions.push_str(&arg_instructions);
arg_string
})
.collect::<Vec<_>>();
let mut get_destination_register = || {
let destination_register = format!("r{}", self.next_register);
self.next_register += 1;
destination_register
};
let mut construct_simple_function_call = |function: &Identifier, variant: &str, arguments: Vec<String>| {
let function_name = function.name.to_string();
let mut names = function_name.split("_to_");
let opcode = names.next().expect("failed to get opcode");
let return_type = names.next().expect("failed to get type");
let mut instruction = format!(" {opcode}.{variant}");
for argument in arguments {
write!(instruction, " {argument}").expect("failed to write to string");
}
let destination_register = get_destination_register();
writeln!(instruction, " into {destination_register} as {return_type};").expect("failed to write to string");
(destination_register, instruction)
};
let (destination, instruction) = match input.variant.name {
sym::BHP256 => construct_simple_function_call(&input.name, "bhp256", arguments),
sym::BHP512 => construct_simple_function_call(&input.name, "bhp512", arguments),
sym::BHP768 => construct_simple_function_call(&input.name, "bhp768", arguments),
sym::BHP1024 => construct_simple_function_call(&input.name, "bhp1024", arguments),
sym::Keccak256 => construct_simple_function_call(&input.name, "keccak256", arguments),
sym::Keccak384 => construct_simple_function_call(&input.name, "keccak384", arguments),
sym::Keccak512 => construct_simple_function_call(&input.name, "keccak512", arguments),
sym::Pedersen64 => construct_simple_function_call(&input.name, "ped64", arguments),
sym::Pedersen128 => construct_simple_function_call(&input.name, "ped128", arguments),
sym::Poseidon2 => construct_simple_function_call(&input.name, "psd2", arguments),
sym::Poseidon4 => construct_simple_function_call(&input.name, "psd4", arguments),
sym::Poseidon8 => construct_simple_function_call(&input.name, "psd8", arguments),
sym::SHA3_256 => construct_simple_function_call(&input.name, "sha3_256", arguments),
sym::SHA3_384 => construct_simple_function_call(&input.name, "sha3_384", arguments),
sym::SHA3_512 => construct_simple_function_call(&input.name, "sha3_512", arguments),
sym::Mapping => match input.name.name {
sym::get => {
let mut instruction = " get".to_string();
let destination_register = get_destination_register();
writeln!(instruction, " {}[{}] into {destination_register};", arguments[0], arguments[1])
.expect("failed to write to string");
(destination_register, instruction)
}
sym::get_or_use => {
let mut instruction = " get.or_use".to_string();
let destination_register = get_destination_register();
writeln!(
instruction,
" {}[{}] {} into {destination_register};",
arguments[0], arguments[1], arguments[2]
)
.expect("failed to write to string");
(destination_register, instruction)
}
sym::set => {
let mut instruction = " set".to_string();
writeln!(instruction, " {} into {}[{}];", arguments[2], arguments[0], arguments[1])
.expect("failed to write to string");
(String::new(), instruction)
}
sym::remove => {
let mut instruction = " remove".to_string();
writeln!(instruction, " {}[{}];", arguments[0], arguments[1]).expect("failed to write to string");
(String::new(), instruction)
}
sym::contains => {
let mut instruction = " contains".to_string();
let destination_register = get_destination_register();
writeln!(instruction, " {}[{}] into {destination_register};", arguments[0], arguments[1])
.expect("failed to write to string");
(destination_register, instruction)
}
_ => unreachable!("The only variants of Mapping are get, get_or, and set"),
},
sym::group => {
match input.name {
Identifier { name: sym::to_x_coordinate, .. } => {
let mut instruction = " cast".to_string();
let destination_register = get_destination_register();
writeln!(instruction, " {} into {destination_register} as group.x;", arguments[0],)
.expect("failed to write to string");
(destination_register, instruction)
}
Identifier { name: sym::to_y_coordinate, .. } => {
let mut instruction = " cast".to_string();
let destination_register = get_destination_register();
writeln!(instruction, " {} into {destination_register} as group.y;", arguments[0],)
.expect("failed to write to string");
(destination_register, instruction)
}
_ => unreachable!("The only associated methods of group are to_x_coordinate and to_y_coordinate"),
}
}
sym::ChaCha => {
let destination_register = get_destination_register();
let mut instruction = format!(" rand.chacha into {destination_register} as ");
match input.name {
Identifier { name: sym::rand_address, .. } => writeln!(instruction, "address;"),
Identifier { name: sym::rand_bool, .. } => writeln!(instruction, "boolean;"),
Identifier { name: sym::rand_field, .. } => writeln!(instruction, "field;"),
Identifier { name: sym::rand_group, .. } => writeln!(instruction, "group;"),
Identifier { name: sym::rand_i8, .. } => writeln!(instruction, "i8;"),
Identifier { name: sym::rand_i16, .. } => writeln!(instruction, "i16;"),
Identifier { name: sym::rand_i32, .. } => writeln!(instruction, "i32;"),
Identifier { name: sym::rand_i64, .. } => writeln!(instruction, "i64;"),
Identifier { name: sym::rand_i128, .. } => writeln!(instruction, "i128;"),
Identifier { name: sym::rand_scalar, .. } => writeln!(instruction, "scalar;"),
Identifier { name: sym::rand_u8, .. } => writeln!(instruction, "u8;"),
Identifier { name: sym::rand_u16, .. } => writeln!(instruction, "u16;"),
Identifier { name: sym::rand_u32, .. } => writeln!(instruction, "u32;"),
Identifier { name: sym::rand_u64, .. } => writeln!(instruction, "u64;"),
Identifier { name: sym::rand_u128, .. } => writeln!(instruction, "u128;"),
_ => unreachable!("The only associated methods of ChaCha are `rand_*`"),
}
.expect("failed to write to string");
(destination_register, instruction)
}
sym::signature => {
let mut instruction = " sign.verify".to_string();
let destination_register = get_destination_register();
writeln!(
instruction,
" {} {} {} into {destination_register};",
arguments[0], arguments[1], arguments[2]
)
.expect("failed to write to string");
(destination_register, instruction)
}
sym::Future => {
let mut instruction = " await".to_string();
writeln!(instruction, " {};", arguments[0]).expect("failed to write to string");
(String::new(), instruction)
}
sym::CheatCode => {
(String::new(), String::new())
}
_ => {
unreachable!("All core functions should be known at this phase of compilation")
}
};
instructions.push_str(&instruction);
(destination, instructions)
}
fn visit_access(&mut self, input: &'a AccessExpression) -> (String, String) {
match input {
AccessExpression::Array(array) => self.visit_array_access(array),
AccessExpression::Member(access) => self.visit_member_access(access),
AccessExpression::AssociatedConstant(constant) => self.visit_associated_constant(constant),
AccessExpression::AssociatedFunction(function) => self.visit_associated_function(function),
AccessExpression::Tuple(_) => {
unreachable!("Tuple access should not be in the AST at this phase of compilation.")
}
}
}
fn visit_call(&mut self, input: &'a CallExpression) -> (String, String) {
let function_name = match input.function.borrow() {
Expression::Identifier(identifier) => identifier.name,
_ => unreachable!("Parsing guarantees that a function name is always an identifier."),
};
let caller_program = self.program_id.expect("Calls only appear within programs.").name.name;
let callee_program = input.program.unwrap_or(caller_program);
let func_symbol = self
.symbol_table
.lookup_function(Location::new(callee_program, function_name))
.expect("Type checking guarantees functions exist");
let mut call_instruction = if caller_program != callee_program {
assert!(
self.program.stubs.get(&callee_program).is_some(),
"Type checking guarantees that imported and stub programs are present."
);
format!(" call {}.aleo/{}", callee_program, input.function)
} else if func_symbol.function.variant.is_async() {
format!(" async {}", self.current_function.unwrap().identifier)
} else {
format!(" call {}", input.function)
};
let mut instructions = String::new();
for argument in input.arguments.iter() {
let (argument, argument_instructions) = self.visit_expression(argument);
write!(call_instruction, " {argument}").expect("failed to write to string");
instructions.push_str(&argument_instructions);
}
let mut destinations = Vec::new();
match func_symbol.function.output_type.clone() {
Type::Unit => {} Type::Tuple(tuple) => match tuple.length() {
0 | 1 => unreachable!("Parsing guarantees that a tuple type has at least two elements"),
len => {
for _ in 0..len {
let destination_register = format!("r{}", self.next_register);
destinations.push(destination_register);
self.next_register += 1;
}
}
},
_ => {
let destination_register = format!("r{}", self.next_register);
destinations.push(destination_register);
self.next_register += 1;
}
}
if func_symbol.function.variant == Variant::AsyncFunction {
let destination_register = format!("r{}", self.next_register);
destinations.push(destination_register);
self.next_register += 1;
}
let output_operands = destinations.join(" ");
if !destinations.is_empty() {
write!(call_instruction, " into").expect("failed to write to string");
for destination in &destinations {
write!(call_instruction, " {}", destination).expect("failed to write to string");
}
}
writeln!(call_instruction, ";").expect("failed to write to string");
instructions.push_str(&call_instruction);
(output_operands, instructions)
}
fn visit_tuple(&mut self, input: &'a TupleExpression) -> (String, String) {
let mut tuple_elements = Vec::with_capacity(input.elements.len());
let mut instructions = String::new();
for element in input.elements.iter() {
let (element, element_instructions) = self.visit_expression(element);
tuple_elements.push(element);
instructions.push_str(&element_instructions);
}
(tuple_elements.join(" "), instructions)
}
fn visit_unit(&mut self, _input: &'a UnitExpression) -> (String, String) {
unreachable!("`UnitExpression`s should not be visited during code generation.")
}
}